网站首页
JSP空间
动态资讯
开源项目
技术文档
资源下载
J2EE资源
客户论坛
在线支付
 
  技术文档>>JAVA>>新手入门>>基础入门>查看文档  
  java繁体中文处理完全攻略     
  文章作者:未知  文章来源:水木森林  
  查看:80次  录入:管理员--2007-11-17  
 
  蔡学镛

许多人用 java 处理到中文资料时,常会出现乱码。关于 java 和中文兼容性的问题,实在让许多程序员为此伤透脑筋,相关的问题每隔几天就会出现在网络上。为了舒缓您紧蹙的眉头,我特别写了这系列文章,解说 java 牵涉到文字时的内部处理方式,供读者参考。读完本系列文章之后,不求甚解者可以治标,充分理解者可以治本。本文贵在原理解说,别光是囫囵吞枣。

快速解决之道

如果你目前正遭遇到 java 和中文不兼容的问题,请你注意下面这几点,说不定问题能马上迎刃而解:

1. 检查操作系统设定:先检查你的操作系统,确定国籍语言资料是「traditional chinese(taiwan)」。国籍语言资料的设定会影响 java 编译器与jre的判断。我之前就是因为国籍资料设定不正确,出了一堆 java 和中文不兼容的怪事。

2. 更新 java 环境版本:改用最新版的 jdk,新版本的 jdk 说不定已经解决你原有的问题。请注意:某些 java ide 所用的编译器和 jre 是不兼容于中文的(我遇过这样的情形),你最好能把 java ide 的 jdk 指到新版的 jdk。另外,如果数据库取回的资料是乱码,换别套或者更新 jdbc 驱动程序试试看。

如果还是无法解决,请详细阅读下面各小节的内容,仔细推敲你的错误所在。

unicode、utf-16、utf-8

java 内部处理字符使用的字序方式是 unicode,这是一种通行全球的编码方式。unicode 因为必须将中、韩、日、英、法、阿拉伯……等许多国家所使用的文字都纳入,目前已经包含了六万多个字符,所以 unicode 使用了 16 个位来为字符编码。因为 unicode 使用了 16 位编码,所以每个字符都用 16 位来储存或传输是很自然的事,这种储存或传输的格式称为 utf-16(是不是很像战斗机的名字)。如果你使用到的字符都是西方字符,那么你一定不会想用 utf-16 的格式,因为体积比 8 位的 latin-1(一种扩充 ascii 的编码)多了一倍。所以 unicode 另有一种储存或传输的格式,叫做 utf-8。utf-8 的格式在编码英文时,只需要 8 位,但是中文则是 24 位,所以中文字出现比例高的地方还是使用 utf-16 比较节省空间。java 的 class file(也就是 bytecode)中有一字段叫做常数区(constant pool),一律使用 utf-8 为字符编码。

关于 unicode 的编码,请查阅「the unicode standard, version 3.0」一书(addison-wesley 出版);关于 utf-8 编码,请查阅「java i/o」一书的 399 页(o´reilly 出版)。关于 java class file 的格式与 constant pool,请查阅「java virtual machine」一书(o´reilly出版)。

unicode 与繁体中文编码的互转

虽然 java 内部完整地使用 unicode,但是你所使用的操作系统可不见得。以繁体中文版的 windows 98 来说,预设的编码方式是 ms950,这是一种兼容于 big 5的编码方式。字符串数据从 windows 一送进 jre,jre 的转码系统马上先把字符串编码由 ms950 转成 unicode,才能进行处理。字符串资料由 jre 一送出给 windows,jre 的转码系统马上先将其由 unicode 转成 ms950,操作系统才能处理。

想知道你的 jdk 或 jre 会用什么样的编码方式来和操作系统沟通,请执行下面的 java 程序:

public class shownativeencoding {

public static void main(string[] args) {

string enc = system.getproperty("file.encoding");

system.out.println(enc);

}

}

如果执行结果不是下面的字符串之一,那么你的操作系统国籍语言设定可能就有问题了:

? big5:这是繁体中文 de facto 标准。

? cns11643:台湾的官方标准繁体中文编码。

? cp937:繁体中文加上 6204 个使用者自定的字符

? cp948:繁体中文版 ibm os/2 用的编码方式。

? cp964:繁体中文版 ibm aix 用的编码方式。

? euc_tw:台湾的加强版 unicode。

? iso2022cn:编码中文的一套标准。

? iso2022cn_cns:编码中文的一套标准,繁体版,袭自 cns11643。

? ms950 或 cp950:ascii + big5,用于台湾和香港的繁体中文 ms windows操作系统

? unicode:有次序记号的 unicode。次序记号占用两个 byte,如果其值是0xfeff,表示使用 big-endian(由大到小)的次序为 unicode 编码;如果其值是 0xffff,表示使用 little-endian(由小到大)的次序为 unicode 编码。

? unicodebig:使用 big-endian(由大到小)的次序为 unicode 编码。

? unicodelittle:使用 little-endian(由小到大)的次序为 unicode 编码。

? utf8:使用 utf-8 为 unicode 编码。

关于 big 5 编码,请查阅「cjkv information processing」一书的附录 h(o´reilly出版)。

编译时的注意事项

编译的时候,如果你不说明原始文件编码方式的话, javac 编译器在读进此原始程序文件,开始编译之前,会先去询问操作系统档案预设的编码方式为何。以繁体中文 windows 98 来说,javac 会先询问 windows 98,得知档案是用 ms950 的方式编码。然后就可以将档案由 ms950 转成 unicode 编码方式,开始进行编译。

通常在编译阶段,会造成的错误有下列几种可能:

1. 如果操作系统的国籍资料设定错误,会造成 javac 编译器取得的编码信息是错的。

2. 较差劲的编译器可能没有主动询问操作系统的编码方式,而是采用编译器预设的编码方式。

3. 如果原始程序不是用编译当时操作系统预设的编码方式存盘的,也会造成错误。比方说,原始程序文件是台湾程序员写的,在繁体中文版的 windows上以 ms950 编码存盘,再经由网络传送到泰国,在泰文版的 windows 上编译(泰文版 windows 预设的档案编码方式是 ms874)。

这种因为原始程序文件编码方式和编译器无法匹配所造成的问题,轻则编译成功但执行时文字出现乱码或出现 error/exception,重则无法成功编译。这时候,你需要主动透过「-encoding」选项来指定原始程序的编码方式,编译器会以你指定的编码为主,不会再去询问操作系统。下面的例子,我们告诉编译器「taiwanclass.java」是以繁体中文版 windows 的「ms950」编码的:

javac ?encoding ms950 taiwanclass.java

如果你手上只有某 class 文件,没有原始程序文件,而且你确定其 constant pool 的utf-8 字段编码错误,你有两种方式可以用来修正编码:

1. 先反编译,取得原始程序,再修改,编译。

2. 或者直接利用 bytecode 编辑软件,直接修改 class 文件。


i/o 转码
java 现行的 io 一律使用 stream 的方式,相关的类别都放在 java.io 中。输出 binary 的资料使用 outputstream 的子类别,输入 binary 的资料使用 inputstream 的子类别,输出文字的资料使用 writer 的子类别,输入文字的资料使用 reader 的子类别。

你可能会觉得很奇怪:「有必要用不同的方式来处理文字和 binary 吗?文字资料不也是 binary 的一种?」没错,其实他们非常类似,最大的差异在于,inputstream/outputstream 会原封不动地传送资料,但是 reader/writer 会将资料当作文字对待,所以 reader/writer 在「必要时」会把(文字)资料转码。什么时候才是所谓的「必要时」呢?

java 的 stream(包括 reader 和 writer)是可以互相串接的。当 reader 的资料来源是另一个 reader 时,不转码,当 reader 的资料来源是一个 inputstream 时,就会转码。当 writer 的资料去处是另一个 writer 时,不转码,当 writer 的资料去处是一个 outputstream 时,就会转码。

由什么码转成什么码?这是可以指定的。因为转码只发生在 reader/inputstream 的交界处与 writer/outputstream 的交界处,所以正是由 inputstreamreader 和 outputstreamwriter 此二类别负责,下面两个 constructor 的第二个参数,正是用来指定转码的方式。

public inputstreamreader(inputstream in, string enc)
throws unsupportedencodingexception;
public outputstreamwriter(outputstream out, string enc)
throws unsupportedencodingexception;

inputstreamreader 负责将 enc 的编码方式转成 unicode(因为资料是从「外部」送过来给「内部」的),outputstreamwriter 负责将 unicode 的编码方式转成 enc(因为资料要从「内部」送给「外部」)。jre 内部当然都一定是用 unicode 编码,而外部的编码就不一定,要看当时的环境为何。你可以透过 getencoding() 的 method,来得知 inputstreamreader 与 outputstreamwriter 的编码方式。

请注意:即使你没用到 inputstreamreader 与 outputstreamwriter,只有用到其它的 reader 和 writer,但是这些 reader 和 writer 内部也很有可能(但非绝对)是直接或间接通到 inputstreamreader 与 outputstreamwriter。比方说:filereader 内部其实是透过一个 inputstreamreader 的中介来将资料从 fileinputstream 取过来的,此时 inputstreamreader 的转码方式是采用 os 的文字编码(以繁体中文的 windows 为例,就是「ms950」)转成 unicode。

如果你清楚地知道你要读写的档案(或资料来源 / 去处)是采用某种编码方式,你也可以主动指定编码方式。但是,请记得抓取可能导致的 unsupportedencodingexception,并务必处理之,不可对此例外置之不理,因为该 jre 有可能没有附上此种编码表(也有可能你的编码名称给错)。

档案 i/o 转码
如果你是在泰文版的 windows 上,想读取用 ms950 编码的繁体中文文字文件,你就必须主动指定编码,不可以直接用 filereader,否则无法成功读取。方法如下:

fileinputstream fis = new fileinputstream(filename);
inputstreamreader reader = new inputstreamreader(fis, "ms950");

然后,透过 reader 读出来的就会是正确的中文。

网络 i/o 转码
如果你的网络程序采用 tcp,那么你可以透过 socket 类别所提供的 getinputstream() 和 getoutputstream() 来得到 inputstream 和 outputstream 对象。如果你是在泰文版的 windows 上,想读取用 ms950 编码的繁体中文文字 tcp 网络串流,你可以用类似上面的技巧来转码。方法如下:

inputstream is = mysocket.getinputstream();
inputstreamreader reader = new inputstreamreader(is, "ms950");

如果你的网络程序采用 udp,你必须把中文字符串转成(或转自)byte 数组。请看下一节「 字符串和 byte 数组的转码 」。

如果你的网络程序采用 rmi,那你完全不用为这部分的转码操心,字符串直接用 unicode 在网络上传递给另一个 jre,不需要转码。

保持刑案现场
如果你不知道你的 i/o 资料来源或去处是用何种编码方式,那么你最好不要用 reader 和 writer,而应该直接用 inputstream 和 outputstream,因为与其被 reader 和 writer 胡乱编码之后造成信息遗失或错乱,不如保持资料的完整不变,留待以后进一步解读。

字符串和 byte 数组的转码
java.lang.string 类别是 java 字符串对象的类别,java 字符串对象既然是活在 jre 内部,当然就一定是用 unicode 编码。如果你需要将 string 对象和 byte 数组互转,你可以使用:

string(byte[] bytes, int offset, int length, string enc);



string(byte[] bytes, string enc);

来将用 enc 编码的 byte 数组,转成 unicode 的 string 对象。你也可以使用 string 对象所提供的:

byte[] getbytes(string enc)

来将 string 对象转成 byte 数组。

另外,你也可以透过 bytearrayinputstream 或 bytearrayoutputstream 串接到 inputstreamreader 或 outputstreamwriter,来达到转码的目的。
 
 
上一篇: java平台i18n 支持概述    下一篇: java反射功能
  相关文档
基于角色的内容管理权限管理模型(图) 11-17
java入门教程:数据类型 11-17
在eclipse中运行带参数的 java 程序 11-17
java事件模型详解 11-17
课程介绍(11)sl-351 java数据库应用程序编程 11-16
java程序员必读:基础篇(1) 11-17
length 属性 (function) 11-16
解决java服务器性能问题研究分析(组图) 11-17
java applet编程小技巧之文字显示 11-16
step-by-step 使用 swt 开发 gui 11-17
java趣味“java网络机器人” 11-17
java动画及自动浏览图片程序的开发 11-17
通过 java 如何实现 aes 密码算法 11-17
java串行端口技术协议 11-17
从txt文件中一行一行读取数字 11-17
java技术与xml常见问题之jaxp 11-17
driveletter 属性 11-16
用java动态代理类实现记忆功能 11-17
classworking工具箱:反射泛型 11-17
jsp新手入门初级教程之jsp概述 11-17
返回首页 | 关于我们 | J网章程 | JSP空间合租 | 客服中心 | 免责声明 | 常见问题 | 参观机房
本站主机空间代理至厦门市华众网络科技有限公司
《中华人民共和国增值电信业务经营许可证》
编号:闽B2-20050079
@2005-2008福建JSP技术网 版权所有 闽ICP备05000928号
技术电话:13616026886
邮箱:admin@fjjsp.com 站长QQ,点击这里给我发消息