`
tantan
  • 浏览: 23620 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论

中文 J2ME中文编码问题

    博客分类:
  • j2me
阅读更多

转自Nokia

1. 简介

本文介绍JavaME中文编码的相关问题,这个问题一度是互联网上的开发者们讨论的热门话题。本文整理和综合了网上众多相关内容,尽可能的为开发者提供一个全面、系统的认识。

文中的代码仅用来说明原理,可能很不完整,缺乏变量定义或者返回值,请谅解。部分代码直接来源于网上的其他资料。

感谢众多开发者在中文编码问题上做出的努力与探索。总结中有什么问题的话,欢迎大家指正:)

 

2. 术语介绍

2.1 ASCII

基于罗马字母表的一套电子计算器编码系统,是单字节的编码方式,每个ASCII字符占用1个字节(8bits),所以ASCII编码最多可以表示256个字符。它是美国信息交换标准委员会(American Standards Committee for Information Interchange)的缩写, 为美国英语通信所设计。

显然ASCII编码用来表示英文字母和字符是足够了的,但是对于中文和日文等众多的文字来说,是远远不够的。

跟ASCII类似的编码还有ISO8859-1。

2.2 UNICODE

双字节编码方式,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本基准转换、处理的要求。UNICODE支持欧洲、非洲、中东、亚洲(包括统一标准的东亚像形汉字和韩国像形文字)的文字。

UNICODE又可以分为“高位在前”和“低位在前”的两种格式,这和CPU的处理方式有点关系。这一点可以通过BOM(Byte Order Mark)来标示,若采用 “低位在前”方式编码,BOM 会表示为 0xFF 0xFE,而在 Unicode 的定义中是不存在 U+FFFE 这个字符的。若采用高位在前方式编码,BOM 会表示为 0xFE 0xFF,而 U+FEFF 刚好是在 Unicode 中的有效字符,代表的是一个不占空间的 space 符号,所以即使没被解释为 BOM,也不会对阅览者产生错误的信息。

但UINICODE也带来一些问题,当美国人看见自己每天最常用的字符需要用两倍的空间来保存时,自然会觉得这是一种浪费,他们一定会说:看看那堆0。于是新的编码方式又诞生了。

2.3 UTF-8

UTF的全称是UCS Transformation Format,即把Unicode转做某种格式的意思。目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32,本文讨论UTF-8格式。

UTF-8是UNICODE的一种变长字符编码,理论上使用1~6个字节来编码UNICODE。

虽然理论上UTF-8最多为6个字节,但是,由于双字节的Unicode最大为0XFFFF,所以双字节的Unicode转为UTF-8后最长为3个字节。

下列字节串用来表示一个字符。 用到哪个串取决于该字符在 Unicode 中的序号。

U-00000000 - U-0000007F: 0xxxxxxx 
U-00000080 - U-000007FF: 110xxxxx 10xxxxxx 
U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx 
U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

上表中的xxx为Unicode编码的二进制数据。例如:“中”字,Unicode编码为4E2D。

Unicode: 4E 2D 	        01001110 00101101
UTF-8: E4 B8 AD 	11100100 10111000 10101101

对于英文来说,UTF-8跟ISO8859-1一样节约;但显然中文等字符将为UTF-8付出更多。

2.4 GB2312

GB2312又称国标码,由国家标准总局发布,1981年5月1日实施,通行于大陆。新加坡等地也使用此编码。它是一个简化字的编码规范,当然也包括其他的符号、字母、日文假名等,共7445个图形字符,其中汉字占6763个。我们平时说6768个汉字,实际上里边有5个编码为空白,所以总共有6763个汉字。

GB2312规定“对任意一个图形字符都采用两个字节表示,每个字节均采用七位编码表示”,习惯上称第一个字节为“高字节”,第二个字节为“低字节”。GB2312中汉字的编码范围为,第一字节0xB0-0xF7(对应十进制为176-247),第二个字节0xA0-0xFE(对应十进制为160-254)。

GB2312具有很多扩展,其中GBK是微软对GB2312的扩展,GB18030则是2000年发布的国家标准,是到目前为止最新的国标汉字编码。

3.JavaME中的字符编解码

3.1 编解码方法

说到JavaME中的字符编码问题,自然要从String类入手,在String类中我们可以找到字符编解码的相关方法:

1. 解码:

public String(byte[] bytes, String enc) throws UnsupportedEncodingException

2. 编码:

public byte[] getBytes(String enc) throws UnsupportedEncodingException

举例来说,“诺基亚”三个汉字的GB2312编码为C5 B5 BB F9 D1 C7。

代码段一,解码试验:

byte[] codes = {0XC5, 0XB5, 0XBB, 0XF9, 0XD1, 0XC7};
String string = new String(codes, “gb2312”);
testForm.append(string);

得到的结果为:诺基亚

代码段二,编码试验:

byte[] codes = “诺基亚”.getBytes(“gb2312”);
for (int i = 0, t = codes.length; i < t; i ++) {
    String hexByte = Integer.getHexString(codes[i]);
    if (hexByte.length() > 2) {
	hexByte = hexByte.subString(hexByte.length() - 2);
    }
    testForm.append(“0X”+ hexByte.subString + “, ”);
}

得到的结果为:0XC5, 0XB5, 0XBB, 0XF9, 0XD1, 0XC7,

3.2 检查设备的编码支持情况

一个最直接的获取编码支持的方法是使用System.getProperty(“microedition.encoding”),可以得到设备的默认的字符编码,以NOKIA设备为例,得到的属性值为ISO8859-1。

然而,通过这个方法的意义并不大。首先,它只能获取到一个编码格式,而一般设备都会支持很多种编码规范;其次,这个属性的数值与虚拟机的实现有很大关系,同样以NOKIA S40v2为例,不论设备的目标市场使用什么语言,这个属性统一为ISO8859-1[参考资料5],显然ISO8859-1对于中文来说是毫无意义的。

再回过头来看看上一节中的两个方法,它们都会抛出一个UnsupportedEncodingException。利用这一点,我们可以自己来做一个设备支持编码规范情况的测试。

boolean isEncodingSupported (String encoding) {
    try {
	"诺基亚".getBytes(encoding);
	return true;
    } catch (UnsupportedEncodingException uee) {
	return false;
    }
}

这里需要提醒的是,对于同一个编码格式来说,可能会有很多种不同的名称,例如Unicode在NOKIA的设备上用的是ucs-2,再例如utf-8来说,utf-8、utf8和utf_8都会有可能。对于这一点,CLDC的规范中并没有给出严格的定义。所以在实际测试的过程中需要充分考虑到这个情况。

3.3 readUTF() 和 writeUTF(String)

CLDC中还有两个方法跟字符编码有关系:DataInputStream中的readUTF()和DataOutputStream中的writeUTF(String)。根据两个方法的Java Doc,writeUTF(String)首先会向输出流中写入字符串编码成UTF8格式后的byte数组长度(2个字节),然后再将这个UTF8的byte数组写入。而readUTF()则是先从输入流中读取2个字节,组成一个short数值,在从输入流中读出这个数值长度的byte数据,再将这个byte数组解码成字符串。详细说明请参考DataInputStream和DataOutputStream的Java Document。

4.具体问题的解决

上一部分中介绍了CLDC平台上所有跟字符编解码相关的API。了解了这些内容以后,就可以结合具体的情况来考虑如何解决中文编码的问题了。

首先,中文编码问题中最常见的情况就是乱码,那么乱码是如何产生的呢?无非是以下几种情况:

  1. 指定的编码格式与数据实际的编码格式不符,造成数据被编码成指定的替代符号或被解释成与源字符完全不同的字符;
  2. 指定的编码格式正确,数据不完整或被篡改,造成数据无法在字符集中找到与其对应的编码;

所以,解决中文编码问题的几个基本思路是:

  1. 存储或传输前,确保数据被正确编码;
  2. 确保存储、读取和传输的过程完整、正确;
  3. 解码时,使用与编码时同样的编码格式;
  4. 确认编码格式是否被设备支持;

 

4.1 RMS存储的中文问题

这个问题完全可以使用readUTF和writeUTF来解决。

用UTF8编码向RecordStore中写入中文:

String content = "中文字符"; 
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
dos.writeUTF(content); 
byte[] bytes = bos.toByteArray();
rs.addRecord(bytes, 0, bytes.length);

从RecordStore中读出UTF8编码的中文:

byte utfBytes[] = rs.getRecord(dbid);
DataInputStream dis=new DataInputStream(new ByteArrayInputStream(utfBytes));
String content = dis.readUTF();

当然,使用UTF8对于中文来说意味着更大的存储空间,所以也可以使用类似Unicode和GB2312等2字节的编码。在此之前,请通过3.2中描述的方式检测编码是否被支持。

用GB2312编码向RecordStore中写入中文:

String content = "中文字符"; 
byte[] bytes = content.getBytes("gb2312");
rs.addRecord(bytes, 0, bytes.length);

从RecordStore中读出GB2312编码的中文:

byte gb2312Bytes[] = rs.getRecord(dbid); 
String content = new String(gb2312Bytes, "gb2312");

4.2 从Resource文件中读取中文

大概可以分为两种情况,一是这个文件遵循某种特定的格式,例如RPG游戏的关卡文件,其中包含地图、事件和对话等数据,文件由特定的程序生成,为自有格式。这种情况基本上可以使用与上一小节中同样的方式来解决。关键是,存储和读取的过程遵从同样的数据和编码格式。

另一种情况是txt文件,可能通过一些文本编辑工具生成。Txt文件中常见的编码格式有Unicode、UTF8、Unicode Big Endian等。在我们读取txt文件之前,最先要确认的就是这个txt文件所使用的编码格式。

以UTF8为例:

in = getClass().getResourceAsStream(filename);
in.read(word_utf);
in.close();
string =new String(word_utf,"UTF-8");

对于Unicode来说,这里引用了参考1中的一段代码,这段代码实际上是在处理“低位在前”的Unicode:

public static String unicodeBytesToString(byte abyte0[], int i)
{
    StringBuffer stringbuffer = new StringBuffer("");
    for(int j = 0; j < i; )
    {
        int k = abyte0[j++]; //注意在这个地方进行了码制的转换
         if(k < 0)
            k += 256;
        int l = abyte0[j++];
        if(l < 0)
            l += 256;
        char c = (char)(k + (l << 8));//把高位和低位数组装起来
         stringbuffer.append(c);
    }
}

对于高位在前的Unicode,可以使用和UTF8类似的方式。请注意,这里的"ucs-2"是针对诺基亚设备的,其他厂商设备可能与此不同,请查阅相关文档或自行测试。

in = getClass().getResourceAsStream(filename);
in.read(word_ unicode);
in.close();
string =new String(word_unicode, "ucs-2");

实际上经过我在NOKIA设备上的测试,对于低位在前的Unicode,也可以使用这个方式。前提是需要确保在数据的最前端添加低位在前的BOM(0XFFFE)。

继续使用“诺基亚”为例,高位在前和低位在前的的Unicode编码分别为:

nokiaBE = {(byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0xfa, (byte)0x4e, (byte)0x9a,}
nokiaLE = {(byte)0xfa, (byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0x9a,  (byte)0x4e,};

new String(nokiaBE, "ucs-2")的结果是“诺基亚”,而new String(nokiaLE, "ucs-2")的结果则是乱码。然后,我们对nokiaLE做出修改:

nokiaLE = {(byte)0xff, (byte)0xfe, 
(byte)0xfa, (byte)0x8b, (byte)0xfa, (byte)0x57, (byte)0x9a,  (byte)0x4e,};

修改后,再次执行new String(nokiaLE, "ucs-2",则得到的结果也是“诺基亚”。

BTW,对于Resouce文件来说,虽然使用Unicode编码存储中文看起来像是比UTF-8要更节约,但是当Resource资源被打成Jar包时,压缩后的文件大小可能很接近。

4.3通过网络读取中文

和前面描述的一样,避免乱码的关键同样是保证编解码使用同样的格式,也就是客户端与服务器段保持同样的字符编码。

对于socket连接来说,传输的内容可以使用自定义的数据格式,所以处理的方式完全和前面两节是相同的,甚至可以向http学习,在数据的开头约定字符编码。

对于http连接,在http数据头中已经约定了编码格式,使用这个编码格式解码即可。另外一个很常见的问题,就是从xml文件中解析中文。对于这一点,在kxml2中已经有了很好的解决方案。

org.kxml2.io.KXmlParser.setInput(InputStream is, String _enc)

你可以通过_enc指定一个编码格式,如果_enc为null,则Parser会根据数据的特性自动尝试各种编码格式。由于kxml为开源项目,如果这里的处理方式需要调整,你也可以自己动手去完善它的功能。

在kxml2中也增加了对wml文件的解析,有兴趣的可以研究一下,这一部分我没有作过尝试。

4.4 JAD中的中文

JAD文件如果需要使用中文,则需要使用UTF8格式。关于如何在JAD中写入UTF8数据,可以有很多方法,可以使用一些Ultral Editor之类的文本工具,或使用一些JAD/MENIFEST生成工具。你甚至可以自己编写一个JAD/MENIFEST的生成工具,对于JavaSE平台来说,这并不是一件太难的工作,你还可以给这个工具赋予更多的功能,例如自动填写vendor和version之类的字段。

特别说明,对于NOKIA Series 40v2等设备来说,通过OTA下载Jad/Jar时,需要在服务器端为JAD添加MIME type时标明encoding为UTF8,否则即使你的JAD确实使用了UTF8格式,安装之后仍然会是乱码。

4.5 Unicode和UTF8的互转

在网上有一些Unicode和UTF8互相转换的代码,是直接通过编码格式去实现的,从前文叙述的Unicode和UTF8之间的关系来看,Unicode和UTF8之间很容易互相转换,计算量也不会太大。这里就不在把这些代码贴出来了,有兴趣的可以自己找一下,或者自己写一个。

但是,实际上Unicode和UTF8这两个编码格式基本是所有设备都会支持的,直接通过String类的编解码方法互转就可以了。

4.6设备不支持的编码格式

如果需要使用设备不支持的编码格式,那么必然将付出一些额外的代价。例如,如果设备不支持GB2312,则你可以有如下的几个选项中选择其一:

  1. 在你的程序中包含一个GB2312的字符对应表;(也许GB2312你还可以接收,但是GB18030要怎么办呢?)
  2. 做一个代理服务器,将GB2312转解码成可以被设备支持的编码;
  3. 放弃GB2312,选择其他编码;
分享到:
评论

相关推荐

    J2ME中文输入Demo

    **J2ME中文输入Demo详解** Java Micro Edition (J2ME) 是Java平台的一个子集,主要用于嵌入式设备和移动设备,如早期的智能手机和平板电脑。在J2ME中,开发人员需要处理资源有限的环境,因此,提供中文输入功能是一...

    j2me 中各种汉字编码相互转换

    在Java ME(J2ME)环境中,汉字编码的相互转换是一项关键任务,因为不同的设备和平台可能使用不同的字符编码标准。本文将详细讲解如何在J2ME中进行UTF-8、Unicode、GB2312以及GBK编码之间的转换,帮助开发者解决跨...

    J2me访问c# Web Services解决了中文乱码

    在本文中,我们将探讨如何使用J2ME(Java 2 Micro Edition)访问由C#编写的Web服务,并解决在传输过程中可能出现的中文乱码问题。首先,我们需要了解J2ME和.NET平台之间的编码差异。 J2ME是Java平台的一个轻量级...

    J2ME 画布实现中文输入

    J2ME通常使用Unicode编码,这是一个包含大量汉字的多字节编码标准。由于J2ME设备内存和处理能力有限,不可能存储所有Unicode字符,因此需要选择性地加载常用汉字。描述中提到“汉字太多,收录的字有限”,这暗示了...

    J2ME中文API帮助文档

    本文将详细解析J2ME中文API,特别是Canvas、Displayable和Alert这三个核心类的功能和使用方法。 首先,Canvas是J2ME中的一个关键类,它提供了直接在设备屏幕上进行图形绘制的能力。Canvas提供了以下几个主要方法: ...

    J2ME低级界面汉字输入

    "J2ME低级界面汉字输入"这个主题主要探讨的是如何在J2ME的低级界面,尤其是Canvas类中实现汉字的输入功能。Canvas是J2ME提供的一种基本绘图界面,它不支持标准的用户输入控件,因此在Canvas上实现汉字输入并不直观,...

    j2me国际化问题

    - **编码问题**:确保所有字符串文件使用统一的字符编码,如UTF-8,以防止乱码问题。 - **效率优化**:由于J2ME运行在资源有限的设备上,因此需要考虑效率。可以预先加载最常用的Locale,或者在需要时按需加载,以...

    J2ME 低级画布实现中文输入

    2. **字符编码**:J2ME平台通常使用Unicode编码,所以你需要熟悉Unicode编码表,尤其是中文字符的部分。每个中文字符对应一个或多个Unicode码点。 3. **输入法引擎**:为了将按键序列转换为中文字符,你需要实现一...

    J2ME 文本本件阅读器

    在J2ME平台上实现UTF-8文本文件的阅读,意味着用户可以阅读包含中文、英文、日文、韩文等多国语言的文本内容。对于那些经常需要处理文本文件的用户,如作家、学生或研究人员,这样的应用无疑提供了极大的便利。 该...

    j2me&game中文

    **J2ME&Game中文:探索移动平台的游戏开发** J2ME(Java 2 Micro Edition)是Java技术在嵌入式设备和移动设备上的应用版本,尤其在2000年代初期,它在手机游戏开发领域占据着主导地位。这个压缩包包含的资源可能是...

    J2ME-trans.rar_j2me_j2me source code_j2me translation

    这个软件可能包含了将英文内容转化为中文的功能,对于学习和开发J2ME应用的开发者来说,它可能是非常有价值的资源。 描述中提到,“J2ME的英汉翻译软件的设计和源码”,这表示这个压缩包不仅提供了翻译软件的最终...

    J2ME游戏课程设计

    在文档方面,提供的中文和英文Word文档很可能是对游戏设计的详细说明,包括游戏规则、设计思路、实现步骤和可能遇到的问题及解决方案。通过阅读这些文档,学生不仅可以学习到具体的编程技巧,还能了解到游戏设计的...

    j2me网络编程大全

    为了正确处理中文等非ASCII字符,通常需要对返回的数据进行编码转换。示例代码中通过`unicodeTogb2312()`方法实现了从Unicode到GB2312的编码转换。 ```java public static String unicodeTogb2312(String s) { if ...

    基于GPRS的J2ME运行平台在嵌入式Linux下的实现

    本文提出的方案成功解决了J2ME平台中的中文化问题,使得该平台能够更好地服务于中文用户群体。具体实现细节可能包括对字体的支持、文本编码转换等方面的工作。 #### 五、总结 本文介绍了在嵌入式Linux环境下实现...

    J2me中文教程MIDP2.0

    ### J2ME中文教程MIDP 2.0 #### 概述 本文档旨在提供一个全面且深入的Java 2 Micro Edition (J2ME)的MIDP 2.0教程,尤其针对移动设备开发。J2ME是Sun Microsystems(现已被Oracle收购)为嵌入式和消费类电子产品...

    j2me开发指南第三讲

    在J2ME平台上处理中文字符可能会遇到编码问题,因为不同的设备和平台可能支持不同的字符集。"彻底解决J2ME中的中文问题"可能涉及以下几个方面: - 使用正确的字符编码,如UTF-8。 - 确保在读写文件或网络传输时正确...

    J2me访问c# Web Services

    在本文中,我们将深入探讨如何让J2ME应用程序访问由C#编写的Web服务,同时解决中文字符传输的问题。J2ME(Java 2 Micro Edition)是一种轻量级的Java平台,广泛应用于移动设备和嵌入式系统。C#则是微软开发的一种...

    j2me 拼音输入

    1. **字符编码**:J2ME支持Unicode编码,但可能需要处理GB2312或GBK等中文编码以兼容旧设备。理解字符编码转换对于处理中文字符至关重要。 2. **拼音库**:需要一个拼音转换库,将用户输入的汉字转换为对应的拼音。...

    J2ME企业级开发-j2me

    在开发J2ME应用时,需要注意中文字符的编码转换问题,确保在不同设备上的兼容性。类如CRMCustomer、CRMLinkman等封装了业务操作,而custSearch、linkSearch等提供了查询界面,共同构成了这个移动客户支持管理系统的...

    手机版姓名转区位码J2ME 实现

    标题中的“手机版姓名转区位码J2ME实现”指的是使用Java 2 Micro Edition (J2ME)技术在移动设备上开发的一个应用,该应用能够将汉字姓名转换成区位码。区位码是汉字字符编码的一种方式,由两个字节组成,分别表示...

Global site tag (gtag.js) - Google Analytics