车东Email:chedongATbigfoot.com/chedongATchedong.com
写于:2002/07最后更新:02/22/200614:42:55版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和及本声明
关键词:mutlibyteencodinglocalei18ni10nchineseISO-8859-1GB2312BIG5GBKUNICODE
内容摘要:
不知道你有没有这样的感受:为什么很少有乱码问题而用Java做却这么麻烦呢?为什么在上能用简体中文查到繁体中文,甚至日文的结果?而且用Google的时候发现它居然能自动根据我使用浏览器的语言选择自动调出中文界面?
很多国际化应用的让我理解了这么一个道理:Unicode是为更方便的做国际化应用设计的,而Java核心的字符是基于UNICODE的,这一机制为应用提供了对中文字的控制(而不是字节)。但如果不仔细理解其中的规范,这种自由反而会成为累赘,从而导致更多的乱码问题:
关于字符集的一些基本概念;试验1:显示系统的环境设置和支持的编码方式;试验2:系统缺省编码方式对Java应用的输入输出影响;试验3:在WEB应用中输出和输出中的字符集问题;关于字符集的准备知识:ISO-8859-1GB2312BIG5GBKGB18030UNICODE为什么会有这么多字符集编码方式?注意:以下说明不是严格定义,一些比喻仅作为方便理解使用。
假设一个字符就是棋盘上的一个棋子,有其固定的坐标,如果需要区别所有的字符,就需要有足够的棋格容纳不同的字符。
英文和欧洲其他语言的单字节字符集:首先对于ISO-8859系列的字符集都想象成一个:2^8=1616=256个格子的棋盘,这样所有的西文字符(英文)用这样一个16字在棋盘上的位置,将以上规则做一个扩展:
如果第1个字符是小于128的仍和英文字符集编码方式保持兼容;如果第1个字符是大于128的,就当成是汉字的第1个字节,这个自己和后面紧跟的1个字节组成一个汉字;其结果相当于在位于128以上的小棋格里每个小棋格又划分出了一个16中文这2个字)但在相应字符集中的坐标不一致,所以GB2312编写的页面用BIG5看就变得面目全非了。而且有时候经常在浏览其他非英语国家的页面时(比如包含有德语的人名时)经常出现奇怪的汉字,其实就是扩展位的编码冲突造成的。
我把GBK和GB18030理解成一个小UNICODE:GBK字符集是GB2312的扩展,GBK里大约有贰万玖仟多个字符,除了保持和GB2312兼容外,繁体中文字,甚至连日文的假名字符也能显示。而GB18030-2000则是一个更复杂的字符集,采用变长字节的编码方式,能够支持更多的字符。关于汉字的编码方式比较详细的定义规范可以参考:[test1-2]:getByteswithplatformdefaultencodinganddecodingasgb2312:string=Helloworldlength=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘?‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘?‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘?‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘?‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS按系统缺省编码重新变成字节流,然后按照GB2312方式解码,这里虽然打印出的是问号(因为当前的英文环境下系统对于255以上的字符是不知道用什么字符表示的,因此全部用?显示)但从相应的UNICODEMAPPING和SHORT值我们可以知道字符是正确的中文但下一步的写入第2个文件html.gb2312.html,没有指定编码方式(按系统缺省的ISO-8859-1编码方式),因此从后面的测试2-2读取的结果是真的‘?‘了[test1-3]:convertstringtoUTF8string=Helloworld涓栫浣犲ソlength=24char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘?byte=-28“uFFFFFFE4short=228“uE4LATIN_1_SUPPLEMENTchar[13]=‘?byte=-72“uFFFFFFB8short=184“uB8LATIN_1_SUPPLEMENTchar[14]=‘?byte=-106“uFFFFFF96short=150“u96LATIN_1_SUPPLEMENTchar[15]=‘?byte=-25“uFFFFFFE7short=231“uE7LATIN_1_SUPPLEMENTchar[16]=‘?byte=-107“uFFFFFF95short=149“u95LATIN_1_SUPPLEMENTchar[17]=‘?byte=-116“uFFFFFF8Cshort=140“u8CLATIN_1_SUPPLEMENTchar[18]=‘?byte=-28“uFFFFFFE4short=228“uE4LATIN_1_SUPPLEMENTchar[19]=‘?byte=-67“uFFFFFFBDshort=189“uBDLATIN_1_SUPPLEMENTchar[20]=‘?byte=-96“uFFFFFFA0short=160“uA0LATIN_1_SUPPLEMENTchar[21]=‘?byte=-27“uFFFFFFE5short=229“uE5LATIN_1_SUPPLEMENTchar[22]=‘?byte=-91“uFFFFFFA5short=165“uA5LATIN_1_SUPPLEMENTchar[23]=‘?byte=-67“uFFFFFFBDshort=189“uBDLATIN_1_SUPPLEMENT第3个试验,将字符流按照UTF8方式编码后,写入第3个测试文件hello.utf8.html,我们可以看到UTF8对英文没有影响,但对于其他文字使用了3字节编码方式,因此比GB2312编码方式的存储要大50%,========Testing2:readinganddecodingfromfiles========[test2-1]:readhello.orig.html:decodingwithsystemdefaultencodingstring=Helloworld世界你好length=20char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘?byte=-54“uFFFFFFCAshort=202“uCALATIN_1_SUPPLEMENTchar[13]=‘?byte=-64“uFFFFFFC0short=192“uC0LATIN_1_SUPPLEMENTchar[14]=‘?byte=-67“uFFFFFFBDshort=189“uBDLATIN_1_SUPPLEMENTchar[15]=‘?byte=-25“uFFFFFFE7short=231“uE7LATIN_1_SUPPLEMENTchar[16]=‘?byte=-60“uFFFFFFC4short=196“uC4LATIN_1_SUPPLEMENTchar[17]=‘?byte=-29“uFFFFFFE3short=227“uE3LATIN_1_SUPPLEMENTchar[18]=‘?byte=-70“uFFFFFFBAshort=186“uBALATIN_1_SUPPLEMENTchar[19]=‘?byte=-61“uFFFFFFC3short=195“uC3LATIN_1_SUPPLEMENT按系统从中间存储hello.orig.html文件中读取相应文件,虽然是按字节方式(半个字)读取的,但由于能完整的还原,因此输出显示没有错误。其实PHP等应用很少出现字符集问题其实就是这个原因,全程都是按字节流方式处理,很好的还原了输入,但这样处理的同时也失去了对字符的控制[test2-2]:readhello.gb2312.html:decodingasGB2312string=Helloworldlength=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘?‘byte=63“u3Fshort=63“u3FBASIC_LATINchar[13]=‘?‘byte=63“u3Fshort=63“u3FBASIC_LATINchar[14]=‘?‘byte=63“u3Fshort=63“u3FBASIC_LATINchar[15]=‘?‘byte=63“u3Fshort=63“u3FBASIC_LATIN最惨的就是输出的时候这些‘?‘真的是问号char了,数据如果是这样就真的没救了[test2-3]:readhello.utf8.html:decodingasUTF8string=Helloworldlength=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘?‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘?‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘?‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘?‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHSgreat!字符虽然显示为‘?‘,但实际上字符的解码是正确的,从相应的UNICODEMAPPING就可以看的出来。========Testing1:writehelloworldtofiles========[test1-1]:withsystemdefaultencoding=GBKstring=Helloworld世界你好length=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘世‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘界‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘你‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘好‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS注意:在新的语言环境中做以上测试需要将源程序重新编译,最早的字节流到字符流的解码过程从JavaC编译源文件就开始了,这个测试和刚才最大的不同在于源文件中的世界你好这4个字是否按中文编码方式编译导程序里的,而不是按字节方式编译成8个字符(实际上对应的是8个字节)在程序里。[test1-2]:getByteswithplatformdefaultencodinganddecodingasgb2312:string=Helloworld世界你好length=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘世‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘界‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘你‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘好‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS在中文环境下,解码和上面缺省的编码是一致的,因此输出一致[test1-3]:convertstringtoUTF8string=Helloworld涓栫浣犲ソlength=18char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘涓‘byte=-109“uFFFFFF93short=28051“u6D93CJK_UNIFIED_IDEOGRAPHSchar[13]=‘栫‘byte=43“u2Bshort=26667“u682BCJK_UNIFIED_IDEOGRAPHSchar[14]=‘‘byte=107“u6Bshort=26219“u666BCJK_UNIFIED_IDEOGRAPHSchar[15]=‘浣‘byte=99“u63short=28003“u6D63CJK_UNIFIED_IDEOGRAPHSchar[16]=‘犲‘byte=-78“uFFFFFFB2short=29362“u72B2CJK_UNIFIED_IDEOGRAPHSchar[17]=‘ソ‘byte=-67“uFFFFFFBDshort=12477“u30BDKATAKANA其实我们用于测试的终端窗口就是一个GBK字符集的应用,这个输出其实都是把UNICODE按GBK字符集解码的效果。========Testing2:readinganddecodingfromfiles========[test2-1]:readhello.orig.html:decodingwithsystemdefaultencodingstring=Helloworld世界你好length=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘世‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘界‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘你‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘好‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS[test2-2]:readhello.gb2312.html:decodingasGB2312string=Helloworld世界你好length=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘世‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘界‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘你‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘好‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS[test2-3]:readhello.utf8.html:decodingasUTF8string=Helloworld世界你好length=16char[0]=‘H‘byte=72“u48short=72“u48BASIC_LATINchar=‘e‘byte=101“u65short=101“u65BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘‘byte=32“u20short=32“u20BASIC_LATINchar=‘w‘byte=119“u77short=119“u77BASIC_LATINchar=‘o‘byte=111“u6Fshort=111“u6FBASIC_LATINchar=‘r‘byte=114“u72short=114“u72BASIC_LATINchar=‘l‘byte=108“u6Cshort=108“u6CBASIC_LATINchar=‘d‘byte=100“u64short=100“u64BASIC_LATINchar[11]=‘‘byte=32“u20short=32“u20BASIC_LATINchar[12]=‘世‘byte=22“u16short=19990“u4E16CJK_UNIFIED_IDEOGRAPHSchar[13]=‘界‘byte=76“u4Cshort=30028“u754CCJK_UNIFIED_IDEOGRAPHSchar[14]=‘你‘byte=96“u60short=20320“u4F60CJK_UNIFIED_IDEOGRAPHSchar[15]=‘好‘byte=125“u7Dshort=22909“u597DCJK_UNIFIED_IDEOGRAPHS结论:如果后台数据采用UNICODE方式的存储然后根据需要指定字符集编码、解码方式,则应用几乎可以不受前端应用所处环境字符集设置的影响
试验2的一些结论:
所有的应用都是按照字节流=世界你好这4个中文字到服务器时:首先浏览器按照GBK方式编码成字节流CAC0BDE7C4E3BAC3,然后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就是这样一个监测客户端浏览器语言设置的例子:
当根据浏览器的头中的字节应用了:
‘世界你好‘length=8ServletRequest‘sCharsetEncoding=nullServletResponse‘sCharsetEncoding=ISO-8859-1char[0]=‘?byte=-54“uFFFFFFCAshort=202“uCALATIN_1_SUPPLEMENTchar=‘?byte=-64“uFFFFFFC0short=192“uC0LATIN_1_SUPPLEMENTchar=‘?byte=-67“uFFFFFFBDshort=189“uBDLATIN_1_SUPPLEMENTchar=‘?byte=-25“uFFFFFFE7short=231“uE7LATIN_1_SUPPLEMENTchar=‘?byte=-60“uFFFFFFC4short=196“uC4LATIN_1_SUPPLEMENTchar=‘?byte=-29“uFFFFFFE3short=227“uE3LATIN_1_SUPPLEMENTchar=‘?byte=-70“uFFFFFFBAshort=186“uBALATIN_1_SUPPLEMENTchar=‘?byte=-61“uFFFFFFC3short=195“uC3LATIN_1_SUPPLEMENT虽然这样的输出结果如果在浏览器中设置用中文字符集也能正确显示,但实际上处理的已经是字节而不是处理中文字符了。ServletRequest和ServletResponse缺省使用ISO-8859-1字符集解码/编码的具体定义请参考:#setCharacterEncoding#setContentType以前能够配置让一个WEB应用能够在GBK方式编码的中文服务器上和按ISO-8859-1方式编码的GNU/Linux上都能够正确的显示中文一直让我迷惑了很久。我仔细想了一下,后来终于想明白了,在一个国际化的应用中:ServletRequest和ServletResponse的编码/解码方式的确不应该根据服务器设置成固定的字符集,而应该是面向客户端语言环境进行输入/输出编码方式的自适应。一个按照国际化规范设计的WEB应用中:
在Servlet的源代码中尽量不要有中文:因为在MVC模式中,Servlet主要是控制器(C)的角色,因此,应该通过ResourceBundle机制由Servlet控制转向到相应的(或者XSLT)中,所以应该将与本地界面语言相关的界面显示的部分从Servlet和后台的模块中完全剥离出来,放到相应的ResourceBundle文件中或者XSLT文件中。这样源程序里完全是英文,编译时完全不需要考虑字符集的问题。如果Servlet实在需要包含中文,则需要设置应用服务器的Javac编译选项,加上-encoding选项成系统缺省的字符集,如果把用中文编写的字符按照英文方式解码编译,然后再按照英文方式输出,虽然结果表面正确,实际上都成了面向字节编程。在Servlet层,应该像GOOGLE搜索引擎那样,设计成能够根据客户端浏览器的语言环境自适应输出,为了判断浏览器的语言Servlet中应该有类似以下的代码:publicvoiddoGetthrowsServletException,IOException//繁体中文浏览器elseif)//日文浏览器elseif)//缺省认为是英文浏览器else...//设置好request的解码方式和response的编码方式后,进行后续的操作。//比如再转向到HelloWorld.gbk.jspHelloWorld.big5.jspHelloWorld.jis.jsp等}而SERVLET缺省将字符集设置为ISO-8859-1也许是标准制定者认为英文的浏览器占大多数吧(而且按照ISO-8859-1方式输出界面往往也是正确的)。
结论:
过以上几个Java试验程序得出的一些结论:
Java环境是基于操作系统上的一个应用,因此,如果操作系统遵循国际化规范:JVM的缺省编码方式可以通过修改操作系统的LOCALE设置实现。对于一个Java应用来说,只要将LINUX的缺省编码方式设置成GBK,其文字编码处理应该和中文Windows平台上的表现是一致的。redhat6.X使用的是基于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方式处理存储。可以把各种区域性的字符集(GB2312BIG5)看成是UNICODE的一个子集。UNICODE存储的数据可以方便的转换成任意字符集。应用使用UTF8方式存储虽然要增加了存储空间,但也可以大大简化前端应用本地化的复杂程度。简体中文输入繁体中文输入简体中文输出繁体中文输出“/“/判断用户语言环境:解码判断用户语言环境:编码“/中间处理过程:UNICODEUTF8编码存储随着UNICODE被愈来愈多的系统和平台支持:Glibc等,但我们应该珍惜一开始就按照国际化规范设计Java,并将其和新发展起来的XML规范相配合,相信符合国际化规范的应用设计从长远来看会展现出更多的优势。
TODO:数据库应用中的字符集问题试验:JDBC
参考文档:Java的国际化设计
Linux国际化本地化和中文化
Linux程序员必读:中文化与GB18030标准
UnicodeFAQ(中文版)Java编程技术中汉字问题的分析及解决
附录:
A.TheUnicode2.0CharacterSetCharacters
Description
“u0000-“u1FFF
Alphabets
“u0020-“u007F
BasicLatin
“u0080-“u00FF
Latin-1supplement
“u0100-“u017F
Latinextended-A
“u0180-“u024F
Latinextended-B
“u0250-“u02AF
IPAextensions
“u02B0-“u02FF
Spacingmodifierletters
“u0300-“u036F
Combiningdiacriticalmarks
“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
HangulJamo
“u1E00-“u1EFF
Latinextendedadditional
“u1F00-“u1FFF
Greekextended
“u2000-“u2FFF
Symbolsandpunctuation
“u2000-“u206F
Generalpunctuation
“u2070-“u209F
Superscriptsandsubscripts
“u20A0-“u20CF
Currencysymbols
“u20D0-“u20FF
Combiningdiacriticalmarksforsymbols
“u2100-“u214F
Letterlikesymbols
“u2150-“u218F
Numberforms
“u2190-“u21FF
Arrows
“u2200-“u22FF
Mathematicaloperators
“u2300-“u23FF
Miscellaneoustechnical
“u2400-“u243F
Controlpictures
“u2440-“u245F
Opticalcharacterrecognition
“u2460-“u24FF
Enclosedalphanumerics
“u2500-“u257F
Boxdrawing
“u2580-“u259F
Blockelements
“u25A0-“u25FF
Geometricshapes
“u2600-“u26FF
Miscellaneoussymbols
“u2700-“u27BF
Dingbats
“u3000-“u33FF
CJKauxiliary
“u3000-“u303F
CJKsymbolsandpunctuation
“u3040-“u309F
Hiragana
“u30A0-“u30FF
Katakana
“u3100-“u312F
Bopomofo
“u3130-“u318F
HangulcompatibilityJamo
“u3190-“u319F
Kanbun
“u3200-“u32FF
EnclosedCJKlettersandmonths
“u3300-“u33FF
CJKcompatibility
“u4E00-“u9FFF
CJKunifiedideographs:HancharactersusedinChina,Japan,Korea,Taiwan,andVietnam
“uAC00-“uD7A3
Hangulsyllables
“uD800-“uDFFF
Surrogates
“uD800-“uDB7F
Highsurrogates
“uDB80-“uDBFF
Highprivateusesurrogates
“uDC00-“uDFFF
Lowsurrogates
“uE000-“uF8FF
Privateuse
“uF900-“uFFFF
Miscellaneous
“uF900-“uFAFF
CJKcompatibilityideographs
“uFB00-“uFB4F
Alphabeticpresentationforms
“uFB50-“uFDFF
Arabicpresentationforms-A
“uFE20-“uFE2F
Combinghalfmarks
“uFE30-“uFE4F
CJKcompatibilityforms
“uFE50-“uFE6F
Smallformvariants
“uFE70-“uFEFE
Arabicpresentationforms-B
“uFEFF