Java繁体中文处理完全攻略
许多人用Java处理到中文资料时,常会出现乱码。关于Java和中文兼容性的问题,实在让许多程序员为此伤透脑筋,相关的问题每隔几天就会出现在网络上。为了舒缓您紧蹙的眉头,我特别写了这系列文章,解说Java牵涉到文字时的内部处理方式,供读者参考。读完本系列文章之后,不求甚解者可以治标,充分理解者可以治本。本文贵在原理解说,别光是囫囵吞枣。
快速解决之道
如果你目前正遭遇到Java和中文不兼容的问题,请你注意下面这几点,说不定问题能马上迎刃而解:
1.检查操作系统设定:先检查你的操作系统,确定国籍语言资料是「TraditionalChinese(Taiwan)」。国籍语言资料的设定会影响Java编译器与JRE的判断。我之前就是因为国籍资料设定不正确,出了一堆Java和中文不兼容的怪事。
2.更新Java环境版本:改用最新版的JDK,新版本的JDK说不定已经解决你原有的问题。请注意:某些JavaIDE所用的编译器和JRE是不兼容于中文的(我遇过这样的情形),你最好能把JavaIDE的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的ClassFile(也就是bytecode)中有一字段叫做常数区(ConstantPool),一律使用UTF-8为字符编码。
关于Unicode的编码,请查阅「TheUnicodeStandard,Version3.0」一书(Addison-Wesley出版);关于UTF-8编码,请查阅「JavaI/O」一书的399页(O'Reilly出版)。关于JavaClassFile的格式与ConstantPool,请查阅「JavaVirtualMachine」一书(O'Reilly出版)。
Unicode与繁体中文编码的互转
虽然Java内部完整地使用Unicode,但是你所使用的操作系统可不见得。以繁体中文版的Windows98来说,预设的编码方式是MS950,这是一种兼容于Big5的编码方式。字符串数据从Windows一送进JRE,JRE的转码系统马上先把字符串编码由MS950转成Unicode,才能进行处理。字符串资料由JRE一送出给Windows,JRE的转码系统马上先将其由Unicode转成MS950,操作系统才能处理。
想知道你的JDK或JRE会用什么样的编码方式来和操作系统沟通,请执行下面的Java程序:
publicclassShowNativeEncoding{
publicstaticvoidmain(String[]args){
Stringenc=System.getProperty("file.encoding");
System.out.println(enc);
}
}
如果执行结果不是下面的字符串之一,那么你的操作系统国籍语言设定可能就有问题了:
?Big5:这是繁体中文defacto标准。
?CNS11643:台湾的官方标准繁体中文编码。
?Cp937:繁体中文加上6204个使用者自定的字符
?Cp948:繁体中文版IBMOS/2用的编码方式。
?Cp964:繁体中文版IBMAIX用的编码方式。
?EUC_TW:台湾的加强版Unicode。
?ISO2022CN:编码中文的一套标准。
?ISO2022CN_CNS:编码中文的一套标准,繁体版,袭自CNS11643。
?MS950或Cp950:ASCII+Big5,用于台湾和香港的繁体中文MSWindows操作系统。
?Unicode:有次序记号的Unicode。次序记号占用两个byte,如果其值是0xFEFF,表示使用big-endian(由大到小)的次序为Unicode编码;如果其值是0xFFFF,表示使用little-endian(由小到大)的次序为Unicode编码。
?UnicodeBig:使用big-endian(由大到小)的次序为Unicode编码。
?UnicodeLittle:使用little-endian(由小到大)的次序为Unicode编码。
?UTF8:使用UTF-8为Unicode编码。
关于Big5编码,请查阅「CJKVInformationProcessing」一书的附录H(O'Reilly出版)。
编译时的注意事项
编译的时候,如果你不说明原始文件编码方式的话,javac编译器在读进此原始程序文件,开始编译之前,会先去询问操作系统档案预设的编码方式为何。以繁体中文Windows98来说,javac会先询问Windows98,得知档案是用MS950的方式编码。然后就可以将档案由MS950转成Unicode编码方式,开始进行编译。
通常在编译阶段,会造成的错误有下列几种可能:
1.如果操作系统的国籍资料设定错误,会造成javac编译器取得的编码信息是错的。
2.较差劲的编译器可能没有主动询问操作系统的编码方式,而是采用编译器预设的编码方式。
3.如果原始程序不是用编译当时操作系统预设的编码方式存盘的,也会造成错误。比方说,原始程序文件是台湾程序员写的,在繁体中文版的Windows上以MS950编码存盘,再经由网络传送到泰国,在泰文版的Windows上编译(泰文版Windows预设的档案编码方式是MS874)。
这种因为原始程序文件编码方式和编译器无法匹配所造成的问题,轻则编译成功但执行时文字出现乱码或出现Error/Exception,重则无法成功编译。这时候,你需要主动透过「-encoding」选项来指定原始程序的编码方式,编译器会以你指定的编码为主,不会再去询问操作系统。下面的例子,我们告诉编译器「TaiwanClass.java」是以繁体中文版Windows的「MS950」编码的:
javac?encodingMS950TaiwanClass.java
如果你手上只有某class文件,没有原始程序文件,而且你确定其constantpool的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的第二个参数,正是用来指定转码的方式。
publicInputStreamReader(InputStreamin,Stringenc)
throwsUnsupportedEncodingException;
publicOutputStreamWriter(OutputStreamout,Stringenc)
throwsUnsupportedEncodingException;
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,否则无法成功读取。方法如下:
FileInputStreamfis=newFileInputStream(fileName);
InputStreamReaderreader=newInputStreamReader(fis,"MS950");
然后,透过Reader读出来的就会是正确的中文。
网络I/O转码
如果你的网络程序采用TCP,那么你可以透过Socket类别所提供的getInputStream()和getOutputStream()来得到InputStream和OutputStream对象。如果你是在泰文版的Windows上,想读取用MS950编码的繁体中文文字TCP网络串流,你可以用类似上面的技巧来转码。方法如下:
InputStreamis=mySocket.getInputStream();
InputStreamReaderreader=newInputStreamReader(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,intoffset,intlength,Stringenc);
或
String(byte[]bytes,Stringenc);
来将用enc编码的byte数组,转成Unicode的String对象。你也可以使用String对象所提供的:
byte[]getBytes(Stringenc)
来将String对象转成byte数组。
另外,你也可以透过ByteArrayInputStream或ByteArrayOutputStream串接到InputStreamReader或OutputStreamWriter,来达到转码的目的。
分享到:
相关推荐
Java 繁体中文处理完全攻略 Java 繁体中文处理完全攻略是指在 Java 编程语言中如何正确地处理繁体中文字符的相关知识点。本文将详细介绍 Java 中的 Unicode、UTF-16 和 UTF-8 编码方式,并探讨 JavaInternally 是...
Java打印程序设计全攻略-4.doc,还有Java打印程序设计全攻略-1.doc,Java打印程序设计全攻略-2.doc,Java打印程序设计全攻略-3.doc,Java打印程序设计全攻略-5.doc
Java打印程序设计全攻略-3.doc,还有Java打印程序设计全攻略-1.doc,Java打印程序设计全攻略-2.doc,Java打印程序设计全攻略-4.doc,Java打印程序设计全攻略-5.doc
"口袋妖怪心金魂银图文完全攻略-全迷宫-全要素-城都关东全篇.docx" 从文件标题、描述、标签和部分内容可以看出,这是一个关于口袋妖怪心金魂银的图文完全攻略,涵盖了全迷宫、全要素和城都关东全篇的内容。下面是从...
Java 打印程序设计全攻略 Java 打印程序设计全攻略是 Java 中一种重要的技术,旨在帮助开发者实现打印功能。本文将从 Java 打印 API、如何实现打印、打印机对话框等几个方面对 Java 打印程序设计进行详细的介绍。 ...
PHP5完全攻略-高清-完整目录-2010年5月,分享给所有需要的人!
旅游攻略-JAVA-基于springBoot旅游攻略平台设计与实现
游戏攻略分享-游戏攻略分享平台-游戏攻略分享平台源码-游戏攻略分享平台java代码-游戏攻略分享平台设计与实现-基于springboot的游戏攻略分享平台-基于Web的游戏攻略分享平台设计与实现-游戏攻略分享网站-游戏攻略...
游戏攻略分享-游戏攻略分享平台-游戏攻略分享平台源码-游戏攻略分享平台java代码-游戏攻略分享平台设计与实现-基于springboot的游戏攻略分享平台-基于Web的游戏攻略分享平台设计与实现-游戏攻略分享网站-游戏攻略...
java攻略-2```
Linux全攻略--squid服务器配置与管理[参照].pdf
普中51单片机开发攻略--A7.普中51单片机开发攻略--A7.pdf
全网最齐全的Java面试题库-附答案-持续更新这个主要Repo用于分享Java面试题,目前已经涵盖Java基础、Java多线程、Java虚拟机、MySQL、Redis、消息中间件、Kafka、RabbitMQ、微服务、Spring、MyBatis、Netty、...
MOBA类游戏攻略分享-MOBA类游戏攻略分享平台-MOBA类游戏攻略分享平台源码-MOBA类游戏攻略分享平台java代码-MOBA类游戏攻略分享平台设计与实现-基于springboot的MOBA类游戏攻略分享平台-基于Web的MOBA类游戏攻略分享...
《普中STM32F1xx开发攻略-V2.2-标准库版.pdf》是一份专为STM32F1xx系列微控制器提供详细开发指南的文档,特别针对使用标准库进行开发的工程师。该文档旨在帮助开发者快速上手STM32的嵌入式系统设计,提高开发效率。 ...
学习本开发攻略主要参考的文档有《STC89Cxx 中文参考手册》,这是 STC 官 方手册,里面包含了 STC89Cxx 单片机内部所有资源介绍,非常详细。大家在学 习 51 单片机的时候可以参考下这个文档,特别是涉及到外设寄存器...
《计算机考研机试攻略 - 满分篇》是一本专为计算机考研者量身定制的实战手册,由N诺课程教研团队精心编撰,集结了CSP、ACM、BAT等领域的大咖智慧。该书旨在帮助考生在短时间内提升机试能力,以应对计算机考研中的...