笔记 by 车东

Java的中文处理学习笔记:Hello Unicode(3)

Servlet应根据浏览器语言设置自动切换字符集配置

首先一个概念:即使是基于Java的WEB应用,在服务器和客户端之间传递的仍然是字节流,比如我从一个中文客户端的浏览器表单中提交“世界你好”这4个中文字到服务器时:首先浏览器按照GBK方式编码成字节流CA C0 BD E7 C4 E3 BA C3,然后8个字节按照URLEncoding的规范转成:%CA%C0%BD%E7%C4%E3%BA%C3,服务器端的Servlet接收到请求后应该按什么解码处理,输出时又应该按什么方式编码行字节流呢?

在目前的Servlet的规范中,如果不指定的话通过WEB提交时的输入ServletRequest和输出时的ServletResponse缺省都是ISO-8859-1方式编/码解码的(注意,这里的编码/解码方式是和操作系统环境中的语言环境是无关的)。因此,即使服务器操作系统的语言环境是中文,上面输入的请求仍然按英文解码成8个UNICODE字符,输出时仍按照英文再编码成8个字节,虽然这样在浏览器端如果设置是中文能够正确显示,但实际上读写的是“字节”,正确的方式是应该根据客户端浏览器设置ServletRequest和ServletResponse用相应语言的编码方式进行输入解码/输入编码,HelloUnicodeServlet.java就是这样一个监测客户端浏览器语言设置的例子:

当根据浏览器的头信息中的"Accept-Language"为zh-cn(中文)时,设置请求的解码方式和输出的字符集编码方式使用GBK:

        //auto detect broswer's languages
        String clientLanguage = req.getHeader("Accept-Language");

        //for Simplied Chinese
        if ( clientLanguage.equals("zh-cn") ) {
            req.setCharacterEncoding("GBK");
            res.setContentType("text/html; charset=GBK");
        }

输出为:

'世界你好' length=4
ServletRequest's Charset Encoding = GBK
ServletResponse's Charset Encoding = GBK
char[0]='世' byte=22 \u16 short=19990 \u4E16 CJK_UNIFIED_IDEOGRAPHS
char[1]='界' byte=76 \u4C short=30028 \u754C CJK_UNIFIED_IDEOGRAPHS
char[2]='你' byte=96 \u60 short=20320 \u4F60 CJK_UNIFIED_IDEOGRAPHS
char[3]='好' byte=125 \u7D short=22909 \u597D CJK_UNIFIED_IDEOGRAPHS

再做一个试验:把程序开头部分的浏览器自动检测功能注释掉,再次的输出结果就是和目前很多应用一样其实是按ISO-8859-1方式解码/编码的“字节应用”了:

'世界你好' length=8
ServletRequest's Charset Encoding = null
ServletResponse's Charset Encoding = ISO-8859-1
char[0]='? byte=-54 \uFFFFFFCA short=202 \uCA LATIN_1_SUPPLEMENT
char[1]='? byte=-64 \uFFFFFFC0 short=192 \uC0 LATIN_1_SUPPLEMENT
char[2]='? byte=-67 \uFFFFFFBD short=189 \uBD LATIN_1_SUPPLEMENT
char[3]='? byte=-25 \uFFFFFFE7 short=231 \uE7 LATIN_1_SUPPLEMENT
char[4]='? byte=-60 \uFFFFFFC4 short=196 \uC4 LATIN_1_SUPPLEMENT
char[5]='? byte=-29 \uFFFFFFE3 short=227 \uE3 LATIN_1_SUPPLEMENT
char[6]='? byte=-70 \uFFFFFFBA short=186 \uBA LATIN_1_SUPPLEMENT
char[7]='? byte=-61 \uFFFFFFC3 short=195 \uC3 LATIN_1_SUPPLEMENT

虽然这样的输出结果如果在浏览器中设置用中文字符集也能正确显示,但实际上处理的已经是“字节”而不是处理中文“字符”了。ServletRequest 和 ServletResponse 缺省使用ISO-8859-1字符集解码/编码的具体定义请参考: http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/ServletRequest.html#setCharacterEncoding(java.lang.String) http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/ServletResponse.html#setContentType()

以前能够配置让一个WEB应用能够在GBK方式编码的中文Windows2000服务器上和按ISO-8859-1方式编码的GNU/Linux上都能够正确的显示中文一直让我迷惑了很久。我仔细想了一下,后来终于想明白了,在一个国际化的应用中:ServletRequest和ServletResponse的编码/解码方式的确不应该根据服务器设置成固定的字符集,而应该是面向客户端语言环境进行输入/输出编码方式的自适应。一个按照国际化规范设计的WEB应用中:

  • 在Servlet的源代码中尽量不要有中文:因为在MVC模式中,Servlet主要是控制器(C)的角色,因此,应该通过ResourceBundle机制由Servlet控制转向到相应的显示器(JSP或者XSLT)中,所以应该将与本地界面语言相关的界面显示的部分从Servlet和后台的模块中完全剥离出来,放到相应的ResourceBundle文件中或者XSLT文件中。这样源程序里完全是英文,编译时完全不需要考虑字符集的问题。

如果Servlet实在需要包含中文,则需要设置应用服务器的Javac编译选项,加上-encoding选项成系统缺省的字符集,如果把用中文编写的字符按照英文方式解码编译,然后再按照英文方式输出,虽然结果表面正确,实际上都成了面向“字节”编程。

  • 在Servlet层,应该像GOOGLE搜索引擎那样,设计成能够根据客户端浏览器的语言环境自适应输出,为了判断浏览器的语言Servlet中应该有类似以下的代码:
    public void doGet (HttpServletRequest req, HttpServletResponse res)
            throws ServletException, IOException {
        //从HTTP请求的头信息中获取客户端的语言设置
        String clientLanguage = req.getHeader("Accept-Language");

        //简体中文浏览器
        if ( clientLanguage.equals("zh-cn") ) {
            req.setCharacterEncoding("GBK");
            res.setContentType("text/html; charset=GBK");
        }
        //繁体中文浏览器
        else if ( clientLanguage.equals("zh-tw") ) {
            req.setCharacterEncoding("BIG5");
            res.setContentType("text/html; charset=BIG5");
        }
        //日文浏览器
        else if ( clientLanguage.equals("jp") ) {
            req.setCharacterEncoding("SJIS");
            res.setContentType("text/html; charset=SJIS");
        }
        //缺省认为是英文浏览器
        else {
            req.setCharacterEncoding("ISO-8859-1");
            res.setContentType("text/html; charset=ISO-8859-1");
        }
        ...
        //设置好request的解码方式和response的编码方式后,进行后续的操作。
        //比如再转向到HelloWorld.gbk.jsp HelloWorld.big5.jsp HelloWorld.jis.jsp等
    }

而SERVLET缺省将字符集设置为ISO-8859-1也许是标准制定者认为英文的浏览器占大多数吧(而且按照ISO-8859-1方式输出界面往往也是正确的)。

结论:

过以上几个Java试验程序得出的一些结论:

  • Java环境是基于操作系统上的一个虚拟机应用,因此,如果操作系统遵循国际化规范:JVM的缺省编码方式可以通过修改操作系统的LOCALE设置实现。对于一个Java应用来说,只要将LINUX的缺省编码方式设置成GBK,其文字编码处理应该和中文Windows平台上的表现是一致的。 redhat 6.X使用linux内核的是基于glibc2.1.X,不支持中文LOCALE,因此无法通过改变LOCALE设置改变JVM的缺省编码方式,linux内核2.4开始基于glibc.2.2.x,对中文LOCALE有了比较好的支持。
  • 不同的JVM对字符集的支持程度不同:
  • 比如:IBM的JVM1.3.0开始支持GB18030,SUN的JVM从1.4开始支持GB18030*
  • 正确的编码方式不一定表示能正确的显示,正确的显示还要需要相应的前端显示系统(字库)的支持
  • 但对于Linux上的服务应用来说,往往只要能确认字符正确的按照指定的方式编码就够了*
  • 如果应用的是基于UNICODE的编码方式处理并使用UTF8字符集做集中存储,这样最便于根据客户端语言环境做本地化输出;

根据以上结论,设计一个适应多语言环境的应用,可以考虑一下2个应用处理模式:

  • (客户端应用或本地化应用)根据LOCALE,让Java应用根据系统LOCALE的缺省的字符集设置进行切换,按系统缺省的字符集进行编码解码,减少应用在编码处理上的复杂程度。 输入字节流 ==>按系统语言字符集设置将字节流解码==> UNICODE处理 ==> 按系统语言字符集设置将UNICODE编码成字节流 ==> 输出字节流
  • (服务器端或跨语言平台应用):在应用的最外端:数据输入输出判断用户语言环境,核心按照UNICODE方式处理存储。可以把各种区域性的字符集(GB2312 BIG5)看成是UNICODE的一个子集。UNICODE存储的数据可以方便的转换成任意字符集。 应用使用UTF8方式存储虽然要增加了存储空间,但也可以大大简化前端应用本地化(i10n)的复杂程度。
简体中文输入 繁体中文输入                 简体中文输出 繁体中文输出
        \   /                                     \     /
   判断用户语言环境:解码            判断用户语言环境:编码
                  \                  /
                  中间处理过程:UNICODE
                           |
                      UTF8编码存储

随着UNICODE被愈来愈多的系统和平台支持:Python Perl Glibc等,但我们应该珍惜一开始就按照国际化规范设计Java,并将其和新发展起来的XML规范相配合,相信符合国际化规范的应用设计从长远来看会展现出更多的优势。

TODO: 数据库应用中的字符集问题试验:MySQL Oracle JDBC

参考文档: Java的国际化设计 http://java.sun.com/docs/books/tutorial/i18n/index.html

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

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

不同版本的JVM支持的编码方式 http://java.sun.com/j2se/1.3/docs/guide/intl/encoding.doc.html http://java.sun.com/j2se/1.4/docs/guide/intl/encoding.doc.html

附录:

Characters Description
\u0000 - \u1FFF Alphabets
\u0020 - \u007F Basic Latin
\u0080 - \u00FF Latin-1 supplement
\u0100 - \u017F Latin extended-A
\u0180 - \u024F Latin extended-B
\u0250 - \u02AF IPA extensions
\u02B0 - \u02FF Spacing modifier letters
\u0300 - \u036F Combining diacritical marks
\u0370 - \u03FF Greek
\u0400 - \u04FF Cyrillic
\u0530 - \u058F Armenian
\u0590 - \u05FF Hebrew
\u0600 - \u06FF Arabic
\u0900 - \u097F Devanagari
\u0980 - \u09FF Bengali
\u0A00 - \u0A7F Gurmukhi
\u0A80 - \u0AFF Gujarati
\u0B00 - \u0B7F Oriya
\u0B80 - \u0BFF Tamil
\u0C00 - \u0C7F Telugu
\u0C80 - \u0CFF Kannada
\u0D00 - \u0D7F Malayalam
\u0E00 - \u0E7F Thai
\u0E80 - \u0EFF Lao
\u0F00 - \u0FBF Tibetan
\u10A0 - \u10FF Georgian
\u1100 - \u11FF Hangul Jamo
\u1E00 - \u1EFF Latin extended additional
\u1F00 - \u1FFF Greek extended
\u2000 - \u2FFF Symbols and punctuation
\u2000 - \u206F General punctuation
\u2070 - \u209F Superscripts and subscripts
\u20A0 - \u20CF Currency symbols
\u20D0 - \u20FF Combining diacritical marks for symbols
\u2100 - \u214F Letterlike symbols
\u2150 - \u218F Number forms
\u2190 - \u21FF Arrows
\u2200 - \u22FF Mathematical operators
\u2300 - \u23FF Miscellaneous technical
\u2400 - \u243F Control pictures
\u2440 - \u245F Optical character recognition
\u2460 - \u24FF Enclosed alphanumerics
\u2500 - \u257F Box drawing
\u2580 - \u259F Block elements
\u25A0 - \u25FF Geometric shapes
\u2600 - \u26FF Miscellaneous symbols
\u2700 - \u27BF Dingbats
\u3000 - \u33FF CJK auxiliary
\u3000 - \u303F CJK symbols and punctuation
\u3040 - \u309F Hiragana
\u30A0 - \u30FF Katakana
\u3100 - \u312F Bopomofo
\u3130 - \u318F Hangul compatibility Jamo
\u3190 - \u319F Kanbun
\u3200 - \u32FF Enclosed CJK letters and months
\u3300 - \u33FF CJK compatibility
\u4E00 - \u9FFF CJK unified ideographs: Han characters used in
China, Japan, Korea, Taiwan, and Vietnam
\uAC00 - \uD7A3 Hangul syllables
\uD800 - \uDFFF Surrogates
\uD800 - \uDB7F High surrogates
\uDB80 - \uDBFF High private use surrogates
\uDC00 - \uDFFF Low surrogates
\uE000 - \uF8FF Private use
\uF900 - \uFFFF Miscellaneous
\uF900 - \uFAFF CJK compatibility ideographs
\uFB00 - \uFB4F Alphabetic presentation forms
\uFB50 - \uFDFF Arabic presentation forms-A
\uFE20 - \uFE2F Combing half marks
\uFE30 - \uFE4F CJK compatibility forms
\uFE50 - \uFE6F Small form variants
\uFE70 - \uFEFE Arabic presentation forms-B
\uFEFF Specials
\uFF00 - \uFFEF Halfwidth and fullwidth forms
\uFFF0 - \uFFFF Specials

💬 历史评论

以下 0 条评论从旧版 Movable Type 博客迁移而来。