`
louies
  • 浏览: 12999 次
  • 性别: Icon_minigender_1
  • 来自: 常州
文章分类
社区版块
存档分类
最新评论

Java程序的中文与Unicode码相互转化(转)

 
阅读更多
Java作为支持多平台的高级程序设计语言自然要支持多种编码方式才能满足程序设计的需要。但是在处理中文&其他编码之间的转换问题时往往出现各种问题,另程序员大伤脑筋。本文着重阐述了Java中文与Unicode编码之间进行相互转化的机理&方法,以求抛砖引玉。

关键字 :Java 中文 Unicode 编码转换
 
约定 :本文中的编码(encoding)和字符集(charset)概念相同

一、Appetite

在进行详细的编码转换原理阐述之前,我们要作两件事情:

1。首先检查操作系统用的语言。以Windows 2003 Server为例,可以在“控制面板”中的“区域和语言设置”中选择你的国家、语言,还有你的操作系统必须支持的语言。国籍&语言的设定会影响JRE的判断情况。也许适当的设定能够帮你解决不少Java语言编码的问题。

2。更新新版本的JDK。因为新版本的JDK往往能够更好的支持新的特性,达到良好的语言迟迟效果。例如JDK5.0就已经更形了JDK1.2中的很多语言问题。

二、正餐

2.1 Unicode编码


2.1.1 Unicode——Java默认的编码

毫无疑问,Unicode作为容纳全球所有语言字符的超级字符集,是Java的首选字符集。Unicode使用两个字节作为编码方式,总共容纳有6万多个字符。因为使用16位进行字符编码,所以也称为UTF-16。然而即使这样,UTF-16也并不能充分囊括所有全世界正在使用或者曾经使用的字符,所以必须对其进行扩充。于是后来的Unicode版本已经扩充到了1,112,064个字符,这种规模已经相当大了。但是这样仍然不能满足Unicode在世界上的需求,所以必须进行必要的扩充。相比预Unicode1.0,后来的2.0版本已经支持扩展字符了,但是并没有真正的加入扩展字符集,这种状况一直持续到了Unicode3.1版,才第一次在Unicode中引入了扩展字符集。但是Unicode的发展脚步并没有停滞,后来出现了Unicode4.0 标准,而这也正好是现在Java5.0版所必须而且已经提供支持的字符集。

显然“对增补字符的支持也可能会成为东亚市场的一个普遍商业要求。政府应用程序会需要这些增补字符,以正确表示一些包含罕见中文字符的姓名。出版应用程序可能会需要这些增补字符,以表示所有的古代字符和变体字符。中国政府要求支持 GB18030(一种对整个 Unicode 字符集进行编码的字符编码标准),因此,如果是 Unicode 3.1 版或更新版本,则将包括增补字符。台湾标准 CNS-11643 包含的许多字符在 Unicode 3.1 中列为增补字符。香港政府定义了一种针对粤语的字符集,其中的一些字符是 Unicode 中的增补字符。最后,日本的一些供应商正计划利用增补字符空间中大量的专用空间收入 50,000 多个日文汉字字符变体,以便从其专有系统迁移至基于 Java 平台的解决方案。

因此,Java 平台不仅需要支持增补字符,而且必须使应用程序能够方便地做到这一点。由于增补字符打破了 Java 编程语言的基础设计构想,而且可能要求对编程模型进行根本性的修改,因此,Java Community Process 召集了一个专家组,以期找到一个适当的解决方案。该小组被称为 JSR-204 专家组,使用 Unicode 增补字符支持的 Java 技术规范请求的编号。从技术上来说,该专家组的决定仅适用于 J2SE 平台,但是由于 Java 2 平台企业版 (J2EE) 处于 J2SE 平台的最上层,因此它可以直接受益,我们期望 Java 2 平台袖珍版 (J2ME) 的配置也采用相同的设计方法。”

UTF-16的编码方式:

UTF-16 使用一个或两个未分配的 16 位代码单元的序列对 Unicode 代码点进行编码。值 0x0000 至 0xFFFF 编码为一个相同值的 16 位单元。增补字符编码为两个代码单元,第一个单元来自于高代理范围(0xD800 至 0xDBFF),第二个单元来自于低代理范围(U+DC00 至 U+DFFF)。这在概念上可能看起来类似于多字节编码,但是其中有一个重要区别:值 0xD800 至 0xDFFF 保留用于 UTF-16;没有这些值分配字符作为代码点。这意味着,对于一个字符串中的每个单独的代码单元,软件可以识别是否该代码单元表示某个单单元字符,或者是否该代码单元是某个双单元字符的第一个或第二单元。这相当于某些传统的多字节字符编码来说是一个显著的改进,在传统的多字节字符编码中,字节值 0x41 既可能表示字母“A”,也可能是一个双字节字符的第二个字节。

2.1.2 节省空间的UTF-8

“如果我只能吃一块巧克力,我绝对不会买上一箱子的巧克力。”

是的,很多时候,特别是我们在处理程序的时候,所使用的并非是所有的Unicode字符,而仅仅是他们其中很小的一个部分,确切的说,这个部分不会比 ASCII多上多少。但是因为使用UTF-16,却不得不为此付出很多的存储空间来存储这些字符,这是一种可耻的浪费。因此,为了便于节省空间,无论是在存储或者传输过程中,如果你只使用到了英文或者拉丁文,那么只需要8位来表示字符就足够了。这就是UTF-8的设计思想。但是,如果是在汉字或者亚洲语言使用频率很高的地方,UTF-16依然将是首选。

但是值得注意的是,因为Unicode本身一直在进行版本更新,UTF-8当然也并非一成不变。对于经修改的过得UTF-8编码,在某些Java API的调用中会出现种种问题,特别是要注意在开发包含增补字符的文本与UTF-8进行转换的时候,可能会出现严重错误。

虽然Java本身对官方修订的UTF-8很熟悉,但是因为Java内部含有一套使用规范编码的机制,因此实际上,Java在使用UTF-8的时候,就并非使用的是Unicode的UTF-8,而是一种叫做“Java modified UTF-8”(经 Java 修订的 UTF-8)或(错误地)直接称为“UTF-8”。而在J2SE5.0种,这种编码被统称为“modified UTF-8”(经修订的 UTF-8)。

“经修订的 UTF-8 和标准 UTF-8 之间之所以不兼容,其原因有两点。其一,经修订的 UTF-8 将字符 U+0000 表示为双字节序列 0xC0 0x80,而标准 UTF-8 使用单字节值 0x0。其二,经修订的 UTF-8 通过对其 UTF-16 表示法的两个代理代码单元单独进行编码表示增补字符 。每个代理代码单元由三个字节来表示,共有六个字节。而标准 UTF-8 使用单个四字节序列表示整个字符。

Java 虚拟机及其附带的接口(如 Java 本机接口、多种工具接口或 Java 类文件)在 java.io.DataInput DataOutput 接口和类中使用经修订的 UTF-8 实现或使用这些接口和类 ,并进行序列化。Java 本机接口提供与经修订的 UTF-8 之间进行转换的例程。而标准 UTF-8 由 String 类、java.io.InputStreamReader OutputStreamWriter 类、java.nio.charset 设施 (facility) 以及许多其上层的 API 提供支持。

由于经修订的 UTF-8 与标准的 UTF-8 不兼容,因此切勿同时使用这两种版本的编码。经修订的 UTF-8 只能与上述的 Java 接口配合使用。在任何其他情况下,尤其对于可能来自非基于 Java 平台的软件的或可能通过其编译的数据流,必须使用标准的 UTF-8。需要使用标准的 UTF-8 时,则不能使用 Java 本机接口例程与经修订的 UTF-8 进行转换。”


UTF-8的编码方式:

UTF-8 使用一至四个字节的序列对编码 Unicode 代码点进行编码。U+0000 至 U+007F 使用一个字节编码,U+0080 至 U+07FF 使用两个字节,U+0800 至 U+FFFF 使用三个字节,而 U+10000 至 U+10FFFF 使用四个字节。UTF-8 设计原理为:字节值 0x00 至 0x7F 始终表示代码点 U+0000 至 U+007F(Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他代码点,这一特性使 UTF-8 可以很方便地在软件中将特殊的含义赋予某些 ASCII 字符。

2.1.3 同胞兄弟——UTF32

如果要问在Unicode家族谁的肚量最大,毫无疑问的是UTF-32。因为采用32位编码方式,所以会使得他的容量特别大!因为会有2的32次方个字符!同样的,会带来相应的问题,就是UTF-32的空间浪费的也比较严重。所以,比般情况下很少使用这种编码。

UTF-32的编码方式:

UTF-32 即将每一个 Unicode 代码点表示为相同值的 32 位整数。很明显,它是内部处理最方便的表达方式,但是,如果作为一般字符串表达方式,则要消耗更多的内存。

2.1.4 三种编码方式的比较

Unicode 代码点 U+0041 U+00DF U+6771 U+10400
表示字形 【Y_J2SE】Java程序的中文与Unicode码相互转化解决之 【Y_J2SE】Java程序的中文与Unicode码相互转化解决之 【Y_J2SE】Java程序的中文与Unicode码相互转化解决之 【Y_J2SE】Java程序的中文与Unicode码相互转化解决之
UTF-32 代码单元
00000041
000000DF
00006771
00010400
UTF-16 代码单元
0041
00DF
6771
D801 DC00
UTF-8 代码单元
41
C3 9F
E6 9D B1
F0 90 90 80
更多的信息可以参见:

关于 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出版)。

 
2.2 Unicode与中文相互转化的问题来源
 
2.2.1 识别你的文件编码
 
虽然Java能够在其内部支持Unicode,但是我们的操作系统并非这样。如果是比较老的windows98 简体中文版,我们只能使用GB2312编码。当我们运行程序的时候,字符串是OS支持的编码,在送进JRE之后,JRE会根据当前操作系统所使用字符集的情况,将字符串转换为unicode进行处理,处理之后,再把他们转化为系统能够识别的字符集中的字符,送出JRE到OS。

如果想要知道你的系统到底使用什么样的字符集与字符打交道,可以使用如下代码片断得到字符集名称:

String enc = System.getProperty"file.encoding");
System.out.println(enc);

可能会得到下列字符集的名称:

GB2313:这是简体中文的标准。
GB18083:这是中文的扩展字符集。
HZ:同样是一种中文标准。
Big5:这是繁体中文标准。
CNS11643:台湾的官方标准繁体中文编码。
Cp937:繁体中文加上 6204 个使用者自定的字符
Cp948:繁体中文版 IBM OS/2 用的编码方式。
Cp964:繁体中文版 IBM AIX 用的编码方式。
EUC_TW:台湾的加强版 Unicode。
ISO2022CN:编码中文的一套标准。
ISO2022CN_CNS:编码中文的一套标准,繁体版,袭自 CNS11643。
MS950 或 Cp950:ASCII + Big5,用于台湾和香港的繁体中文 MS Windows操作系统。

2.2.2 问题来源

在Javac编译期间,也会先从OS中取得现在使用的字符集,此处设为A,之后把送入的字符串转化为Unicode编码,在编译之后再从Unicode转化为A型字符集。因此:

  1. 当你的操作系统国际设定错误,编译时就会产出错误的字符集编码。
  2. 一些比较lj的编译器会按照预先设定的字符集,而非OS所使用的字符集进行编码。
  3. 原是文件存盘时使用的字符集与编译器所使用的字符集无法匹配也会产生错误。

对于1和2,很好理解。对于3,例如我们使用的OS时GB2312,但是存盘时使用的编码字符集时UTF-8,这样java编译器编译文件的时候,就把UTF-8字符集当成GB2312字符集来处理,这样当然会出错。

可以使用一下代码片断来以制定的编码方式编译Java文件。

javac -encoding GB2312 TestEncoding.java
 
然而,有时候不得不面临另外一种不幸的情况,即我们手头只有字节码文件,但是原来类中的常量中肯定存在编码问题。只有先反编译字节码文件,修改文件之后再重新编译。
 
2.3 解决之道
 
2.3.1 I/O神功
 
幸好,因为Java中强大的IO接口,我们才有机会将上面的不幸化解。
 
Java中所有的IO都是通过流来完成的。对于二进制数据的输入,InputStream是所有输入流的基类;而对于所有的二进制输出,OutputStream则是所有输出流的基类。在java.io包中的所有类几乎都与这两个类有着千丝万缕的联系。而对于各种文字数据,Writer类则是所有文字输出的祖先类,Reader也一样是所有文字输入类的祖先类。
 
但是文字毕竟也是“binary”,为什么要单独给它们编写Reader和Writer类呢?问题在于, InputStream与OutputStream会照本宣科的解读所有输入的数据为binary,而Reader和Writer才真正的把文字当成文字,并且在需要的时候将其转换。这种需要的转换的情况存在与XXXXer类与XXXXStream作为对口时才会发生。例如,当Reader类的来源是一个InputStream时,或者Writer的数据目标是一个OutputStream时就会发生转码。由此可知,这种转换实际上发生在Reader与 InputStream或者是Writer与OutputStream的交界处。幸运的是Java强大而庞大的类库为我们提供了这种转换机制,函数原形如下:
 
public InputStreamReader(InputStream in, String encoding) throws UnsupportedEncodingException;
public OutputStreamWriter(OutputStream out, String encoding) throws UnsupportedEncodingException;
勿庸置疑,JRE内部使用Unicode编码,但是外部环境的编码方式就不一定了。可以使用
getEncoding()方法得知外界使用的编码方式。
 
当然,如果你清楚的知道文档的来去和系统的编码方式,你可以自己指定。代码如下:
 
FileInputStream fis = new FilInputStream(new File("hello.txt"));
InputStreamReader isr = new InputStreamReader(fis,"GB2312");
 
这样可以正确的读出文件中的字符。
 
如果是除了RMI以外的网络连接方式进行读取,也需要使用相应的方法获取相应的输入输出流,之后代码实现类似上例。但是如果你使用的是UDP,那么情况就例外了:你必须把中文字符串转换为数组。那么RMI为什么不用进行编码转换呢?很简单,因为RMI是把 Unicode传给另外一个远程主机,所以不存在编码转换!
 
注意:
  1. 如果你不能确定你的数据来源或者流向,那么最好不使用Reader和Writer,因为这样可能造成不必要的信息损失。与其这样,不如保持其二进制编码的完整性,留作以后进一步处理。
  2. 有时候,Reader和Writer之间进行通信的时候也可能出现编码错误,原因在于他们之间直接或者间接的使用到了I/O流,这样就可能导致编码转换时出现不统一的情况。

2.3.2 字符串与字节数组

Java的String类提供了非常丰富的功能借助与此,我们也能达到编码转换的功能。

常用的String构造函数如下:

String(byte[] bytes, int offset, int length, String charset);
String(byte[] bytes, String charset);

以上方法可以通过byte数组创建指定字符集的字符串,而下面的方法:

byte[] getBytes(String charset);

则可以将String转化为指定字符集的byte数组。

此外,还可以通过ByteArrayInputStream 或 ByteArrayOutputStream 串接到 InputStreamReader 或 OutputStreamWriter,来达到转码的目的。

2.4 其他问题和解决办法

然而Java本身涉及到编码的问题不止这些。曾经有位朋友编写一个可视花的zip应用程序。非常不幸的是,由于Java 本身的编码问题,使得他的程序在存储文件时,如果文件名是含有中文的,那么存储后的文件名不能够正确显示。这个问题困扰了他很久,虽然使用了本文中所提到过的方法,但是依然不能够解决问题。无奈,在网络上查找了相关资料,发现如果不用java自己的zip包而改用Apache的zip包问题能够得到解决。

这就提示我们说,有的时候,当你面临Java的编码问题时,不妨利用第三方的工具包尝试解决往往能够收到不错的效果。

三 总结

综上,本文讨论的Java字符编码问题的来龙去脉,并且给出了相应的解决方法。相信凭借着对问题根源的了解,Java的字符编码问题一定能够在实际中得到解决。

分享到:
评论

相关推荐

    Java String与Byte类型转换

    在Java编程中,String对象和Byte...总之,Java中的String与Byte类型的转换是编程中不可或缺的部分,尤其在网络编程中,理解这两种类型之间的转换方式及其在网络数据交换中的作用,对于编写高效、可靠的程序至关重要。

    Java进制/时间/日期/字符串/流算法大全

    Java 的16 进制与字符串的相互转换函数 JAVA 时间格式化处理 将毫秒转化为日期 文本的倒序输出 判断一个数字是奇数还是偶数 用Hibernate 实现分页 35 选7 彩票程序 获取GMT8 时间 中文乱码转换 Big5 字与Unicode 的...

    java 数据类型转换

    在Java编程语言中,数据类型转换是至关重要的概念,它涉及到不同类型的变量之间的相互转化。Java分为两大类数据类型:基本数据类型(如int、char、float等)和引用数据类型(如类、接口和数组)。本文将深入探讨Java...

    java中char对应的ASCII码的转化操作

    在实际应用中,字符和ASCII码的转换在字符处理、文件输入输出等操作中十分常见,掌握其转换方法以及转换中可能出现的问题对于开发高质量的Java程序来说非常重要。此外,随着计算机科学和编程技术的发展,Unicode字符...

    Java大学真题基础练习.doc

    8、在 Java 中,字符类型采用 Unicode 编码方案,每个 Unicode 码占用 2 个字节(byte),每个字节等于 8 比特位(bit),因此每个 Unicode 码占用 16 个比特位。 9、在 Java 中,源文件名与 public 类型的类名必须...

    Java复习笔记

    **常用类**包括一些基本数据类型的包装类,如`Integer`, `Double`, `Float`等,它们提供了将基本数据类型与字符串相互转化的方法,如`intValue()`, `parseInt()`, `doubleValue()`, `parseDouble()`, `floatValue()`...

    Java面试题、笔试题

    * UTF-16:ISO试图创建一个全新的超语言字典,世界上所有语言都可通过这本字典Unicode来相互翻译,而UTF-16定义了Unicode字符在计算机中存取方法,用两个字节来表示Unicode转化格式。 * UTF-8:UTF-16统一采用两字节...

    GB2312UTF-8字符互转

    这两种编码方式在处理中文字符时各有特点,有时需要进行相互转换。 GB2312(全称为“汉字编码字符集国家标准”,又称GB2312-80)是中国大陆早期制定的一套汉字编码标准,主要用于简体中文。它包含了6763个常用汉字...

    字符编码和字符集研究

    例如,如果一个Java程序从GBK编码的文本文件中读取数据,那么它需要先将GBK编码的字符转换为Unicode,再进行处理。 在实际应用中,处理字符编码的不当可能导致乱码问题。例如,当一个UTF-8编码的字符串被误认为是...

    各大数据库类型与JDBC中介数据类型的转换对比

    这有助于确保数据能够在Java应用程序与不同数据库之间正确、高效地传输。本文将详细介绍MySQL、SQL Server、Oracle、DB2等主流数据库的数据类型,并将其与JDBC的标准数据类型进行对比分析。 #### 数据库数据类型...

    Java流(文件读写操作).docx

    Java流是Java编程语言中处理输入输出操作的重要概念,它允许程序在内存和外部设备之间高效地传输数据。Java流分为两大类:字节流和字符流,它们根据数据流动的方向和处理的数据类型进一步细分。 1. **按数据流动...

    编译原理与技术练习题汇总.doc

    编译原理与技术是计算机科学中的核心课程,主要研究如何将高级程序设计语言转化为机器可执行的指令。本篇内容将围绕相关练习题进行深入阐述。 1. 高级程序语言需要编译程序的原因在于,高级语言更加接近人类思维,...

    IO流讲解-LingRan.pptx

    Java中的IO流是进行输入/输出操作的核心工具,它允许程序与各种数据源(如键盘、文件、网络连接等)进行交互。IO流的概念源于Java的`java....理解IO流的原理、分类和使用场景,对于编写高效、可靠的Java程序至关重要。

    2021-2022计算机二级等级考试试题及答案No.4267.docx

    1. DriverManager 类在 Java 中是用于管理数据库驱动程序的,它的主要作用是加载和注册数据库驱动,从而建立与数据库的连接。正确答案:D。 2. SMTP(Simple Mail Transfer Protocol)是互联网上用于发送电子邮件的...

    《编码---隐匿在计算机软硬件背后的语言.上》 高清 PDF

    7. **编译器与解释器**:书中详细探讨了编译器和解释器的作用,它们将高级编程语言转化为机器可理解的形式,使得程序员可以使用更抽象的语言编写程序。 8. **高级编程语言**:通过对比不同的编程语言,如C、Java、...

    技术面试题汇总

    - 加载过程将字节码文件读入内存,并转化为运行时数据结构。 **36. char型变量中能不能存贮一个中文汉字?为什么?** - `char`类型可以存储中文汉字。 - `char`类型在Java中采用Unicode编码,支持中文字符。 **37. ...

    新版Android开发教程.rar

    程序可以采用 JAVA 开发,但是因为它的虚拟机 (Virtual Machine) Dalvik ,是将 JAVA 的 bytecode 转成 自 己的格式,回避掉需要付给 SUN 有关 JAVA 的授权费用。 对手机制造者的影响 � Android 是款开源的移动计算...

    MethodLibrary

    【标题】"方法库"(MethodLibrary)是一个专注于字符串处理的简单Java程序。这个程序集成了多种方法,用于对文本进行各种操作,从而实现一些有趣的功能。开发者可以通过这些方法来处理和分析字符串,提高代码的复用...

    编码的奥秘

    而程序设计语言编码则涉及编译器和解释器如何将高级语言转化为机器语言,如C、Java、Python等语言的编译和解释过程。 在描述中提到的“上课时同学们互相传阅的资料”,这可能涵盖了从基础的编程概念到高级的编码...

    计算机基础原理

    8. **编程语言**:人类编写程序时使用高级编程语言,如Python、Java、C++。编译器或解释器将这些高级语言转换为机器可理解的二进制指令。 9. **网络通信**:计算机通过网络互相连接,实现数据交换。TCP/IP协议栈是...

Global site tag (gtag.js) - Google Analytics