从 汉化 到 国际化
    ——UniCode inside, Localization outsite

作者: 车东 Email: chedongATbigfoot.com/chedongATchedong.com

写于:2002/08 最后更新:

版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本声明
http://www.chedong.com/tech/unicode_java.html

关键词:i18n java jvm locale  乱码

内容摘要:

Java对输入输入首先有一个“字节流”到“字符流”之间的编码/解码过程,这个设置是根据系统配置决定的,为什么PHP之类的应用很少有字符集问题而Java有很好的国际化机制,却经常出现乱码问题呢?

简单的举例:
有一个包含“你好”这2个中文字的文件实际上是4个字节组成的:C4 E3 BA C3
在英文操作系统中缺省的编码解码方式是缺省编码方式是ISO8859,所以直接从文件中读取的结果是4个的字节,按ISO8859解码后在程序中操作的是4个Java字符,虽然每个JAVA字符是16位Unicode,但每个字符仍是8位字节的映射\u00C4\u00E3\u00BA\u00C3,因此处理的仍是“英文”。而显示过程中,是浏览器将字节流正确的显示成了相应的中文。

而一个Java应用在GBK编码方式的操作系统中,直接从文件中读取4个的字节后,按GBK方式解码后是2个16位的Java字符\u4F60\u597D,每个字都是相应Unicode的CJK区块所对应的中文。

更多的例子请参考:Java的中文处理学习笔记

这也就是为什么在php等应用很少出字符集问题的原因:在服务器端环境缺省一般是英文(ISO8859-1),等于全部处理使用的都是按字节方式处理的。数据输入输出过程中编码方式完全不被改动,因此乱码问题很少出现。而Java实际上提供了把每个中文直接当成1个“字”而不是2个字节处理的机制,主要的乱码问题往往是输入输出时编码解码方式不一致造成的。而且通过Unicode机制,程序除了实现程序界面根据本地化的适应外,甚至程序处理的内容本身的在不同字符集的操作系统中也是可以通用的,比如:在繁体中文操作系统中编辑的内容,在简体中文操作系统中也能正常的查询。以下例子可能更说明问题:

  1. JAVA应用的中文问题:如何通过GNU/Linux系统的本地化设置让JAVA应用支持中文
  2. Java Web应用设计中的中文问题:通过web.xml设置解决URLEncoder.encode()方法和系统缺省编码方式相关的问题
  3. 以GOOGLE的搜索引擎为例:说明如何将国际化和本地化应用到自己的应用设计中(UniCode inside Localization outsite)

通过GNU/Linux系统的本地化设置让JAVA应用支持中文

Java 编程技术中汉字问题的分析及解决这篇直到最近还经常被一些网站转贴的文章中有一个例子说明了很多中国程序员遇到汉字乱码问题的思路:"GB2312 it"(汉化)

原文如下:
>>>>>>>
......前不久,我的一位技术上的朋友发信给我说,他终于找到了 Java Servlet 中文问题的根源。两周以来,他一直为 Java Servlet 的中文问题所困扰,因为每面对一个含有中文字符的字符串都必须进行强制转换才能够得到正确的结果(这好象是大家公认的唯一的解决办法)。后来,他确实不想如此继续安分下去了,因为这样的事情确实不应该是高级程序员所要做的工作,他就找出 Servlet 解码的源代码进行分析,因为他怀疑问题就出在解码这部分。经过四个小时的奋斗,他终于找到了问题的根源所在。原来他的怀疑是正确的, Servlet 的解码部分完全没有考虑双字节,直接把 %XX 当作一个字符。(原来 Java Soft 也会犯这幺低级的错误!)

如果你对这个问题有兴趣或者遇到了同样的烦恼的话,你可以按照他的步骤对 Servlet.jar 进行修改:

找到源代码 HttpUtils 中的 static private String parseName ,在返回前将 sb(StringBuffer) 复制成 byte bs[] ,然后 return new String(bs,”GB2312”)。作上述修改后就需要自己解码了:

HashTable form=HttpUtils .parseQueryString(request.getQueryString())或者

form=HttpUtils.parsePostData(……)

千万别忘了编译后放到 Servlet.jar 里面。
......
<<<<<<<<<

请问这位“高级”程序员几个问题:

  1. 如果这是一个商业产品的话,难道客户需要你Hacking过的Servlet.jar才运行这个应用吗?
  2. 难道这个产品只能用在中文平台的GB2312上吗?如果是日文应用怎么办,如法Hacking吗?

也许我错了,但我的感觉是犯低级错误的不是JAVA:

  1. 首先,在Servlet层就不应该考虑中文输出的问题,因为,在MVC的设计模式中,Servlet主要的角色是Contrallor,所以,在这一层中,数据应该最好还是Unicode形式,在最后让Jsp或者通过xslt做针对客户端浏览器的输出时,再需要考虑本地字符集编码的问题。
  2. 作为一个标准的国际化应用,JAVA应用的缺省编码方式不应该是在WEB应用这一层设置的,而是JVM根据系统缺省编码方式根据操作系统的环境设置(locale, 包括字符集,日期格式等本地化环境)改变来实现。

如何设置可以让GNU/Linux从系统层次就支持中文编码(让JVM缺省的file.encoding就按照中文GB2312或者GBK进行编码解码)呢?

以上这篇文章发表在2000年年底,当时的GNU/Linux的内核是基于glibc-2.1开发的,而GLIBC2.1对中文的locale支持还有限,因此,在GNU/Linux上不能根据locale的设置将系统缺省的编码方式变成GB2312,从而改变JVM缺省的编码方式。关于GNU/Linux对l10n的支持请看:GNU/Linux程序员必读:中文化与GB18030标准所以在redhat6.x下,无论你怎么设置locale,系统缺省的缺省file.encoding都是ISO_8859_1。

在redhat7.x 系统内核所基于的glibc-2.2.x对l10n有了更完整的支持,所以可以通过设置
LC_ALL=zh_CN.GB2312;export LC_ALL
LANG=zh_CN.GB2312;export LANG
让系统缺省的编码方式变成GB2312。JVM会根据系统的缺省编码方式设置系统的file.encoding属性。之后,应用中任何字节流到字符流的转换,JVM都会按照这一系统缺省编码方式进行转换。因此在基于glibc-2.2以上的GNU/Linux上是可以通过locale的设置来改变系统缺省的编码方式,从而改变上层Java应用的缺省编码、解码方式的。

locale设置对JVM file.encoding的影响可以我通过做过的一个试验说明:hello_unicode.html

由此可见:

  1. GNU/Linux是依靠GNU的工具发展起来的:没有GNU就没有GNU/Linux。所以GNU/Linux对本地化的支持,也是在核心的glibc-2.2.x对中文locale有了更好的支持以后才逐步发展起来的。
  2. GNU/Linux对国际化的支持远远落后于WINDOWS SOLARIS等商业操作系统:2年甚至更多。

通过web.xml设置解决URLEncoder.encode()方法和系统缺省编码方式相关的问题

在我所理解的范围内,J2SE 1.3中非常不符合Java的国际化规范的是在使用URLEncoder的时候:
比如在中文WIN98上运行的应用,使用URLEncoder.encode(String s)时:比如“中文”这2个字符直接被Encoding的话结果是"%3F%3F"=>"??"。原因很简单,“中文”在encode()过程中应该先按GBK编码方式编码成4个字节(\u00d6\u00d0\u00ce\u00c4)后再进行URLEncoding才是正确的。这个在J2SE1.4中也修正了。方法encode(String s)已经不鼓励使用,取而代之的是除了需要进行URLEncoding的字符串外,同时需要指定字符串编码方式的encode(String s, String enc)。这样,URLEncoder就可以和系统缺省的编码方式无关了。

在J2SE1.3下一个WEB应用如果有这个问题可以通过在WEB-INF/web.xml中设置character-encoding来解决:
<web-app character-encoding="your_system_default_file.encoding">
...
</web-app>

比如:产品是在中文WINDOWS98中运行,系统缺省字符集是用GBK,则这个应用的web.xml需要设置成:
<web-app character-encoding="GBK">
...
</web-app>

UniCode inside, Localization outsite

以上2个方法仍然只是让应用更方便地本地化了,而应用本身并不是真正的国际化应用。设想一下如何设计一个全球的论坛系统:可以让中文和日文的用户都可以方便的浏览发表呢?在数据中间处理阶段应该以那种字符集存储呢?答案很简单:UniCode。以前很多文章都有关于如何设计一个国际化界面的介绍,只是应用的本地化界面输出,但很少提及数据在中间处理过程中如何适应国际化。

输入和存储阶段就用UniCode方式进行处理和存储,以方便应用以后的国际化。GOOGLE的设计就是一个非常好的国际化应用榜样,我以GOOGLE搜索引擎的国际化支持为例说明如何实现国际化应用的设计。

GOOGLE用户经常有这样的感觉:

  1. 为什么我第一次去GOOGLE,出现的就是中文的界面?
  2. 为什么在所有网站中查中文:有时候还会匹配到日文网站的结果?比如:就以"google 秘密"这个查询为例:我们在输入框输入"google 秘密"
    http://www.google.com/search?hl=zh-CN&newwindow=1&q=google+%C3%D8%C3%DC&btnG=Google%CB%D1%CB%F7&lr=

首先我将GOOGLE对查询的处理流程简单的说明如下:

  1. 客户端浏览器输入;
  2. 查询字符串按客户端系统编码方式(GBK)转换成字节流,并URL Encode后传给GOOGLE;
  3. GOOLGE将输入的字符串URL Decode后,按照客户端的系统编码方式将这个字符串(字节串)解码成UniCode
  4. 查询过程,完全是基于UniCode的匹配过程,比如对于“中文”这2个字在简体繁体中文和日文里都有,因此无论是何种语言的页面包含这2个字的页面都能匹配上。
  5. 结果集输出:将查询结果集的内容(UNICODE)按客户端系统编码方式(GBK)“编码”成的字节流,返回给浏览器

具体说明:

从以上的分析中我们可以看出:UniCode非常漂亮的解决了应用的国际化问题。对于应用前端来说,剩下的工作就是根据本地编码环境进行本地化的过程了。

  1. 数据从输入的开始,就全部先转换成UniCode,然后再进行处理,并按照UniCode方式集中存储(UniCode inside)
  2. 数据输出过程中,只是在最后输出到客户端的时候,按照客户端的本地化设置将UniCode数据转换成本地字符集,并配以相应语言/字符的界面(Localization outside)

如果应用的开发只是满足于在国内市场自给自足,“汉化”的思路的大量出现是很自然的。但要是把“汉化”比作UCDOS和RichWin的话,那么这种汉化方式迟早要被内核汉化的WIN95淘汰的。毕竟核心级别对国际化的支持才是一个真正简化前端应用设计、通用的解决方案。Microsoft和Sun等国际化大公司的产品从一开始就是为全球市场设计的,因此对国际化的支持一致非常重视。相比之下国内软件行业对相应国际标准显然重视不足,也很少积极地参与相关标准制定。


 

参考文档:
Java i18n
http://java.sun.com/docs/books/tutorial/i18n/index.html

Linux 国际化本地化和中文化
http://www.linuxforum.net/doc/i18n-new.html

GNU/Linux 程序员必读:中文化与GB18030标准
http://www.ccidnet.com/tech/os/2001/07/31/58_2811.html

UniCode FAQ
http://www.cl.cam.ac.uk/~mgk25/UniCode.html
http://www.linuxforum.net/books/UTF-8-UniCode.html (中文版)

Java 编程技术中汉字问题的分析及解决
http://www-900.ibm.com/developerWorks/cn/java/java_chinese/index.shtml

汉字的编码方式:
http://www.unihan.com.cn/cjk/ana17.htm

*注释:l10n i18n都是缩写:用的是英文单词的首尾字母和其间字母个数
l10n: localization 本地化
i18n: internationalization 国际化

原文出处:<a href="http://www.chedong.com/tech/unicode_java.html">http://www.chedong.com/tech/unicode_java.html</a>
<<返回