扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
不知道你有没有这样的感受:为什么PHP很少有乱码问题而用Java做WEB应用却这么麻烦呢?为什么在Google上能用简体中文查到繁体中文,甚至日文的结果?而且用Google的时候发现它居然能自动根据我使用浏览器的语言选择自动调出中文界面?
很多国际化应用的让我理解了这么一个道理:Unicode是为更方便的做国际化应用设计的,而Java核心的字符是基于UNICODE的,这一机制为应用提供了对中文“字”的控制(而不是字节)。但如果不仔细理解其中的规范,这种自由反而会成为累赘,从而导致更多的乱码问题:
关于字符集的准备知识:
ISO-8859-1 GB2312 BIG5 GBK GB18030 UNICODE 为什么会有这么多字符集编码方式?
注意:以下说明不是严格定义,一些比喻仅作为方便理解使用。
假设一个字符就是棋盘上的一个棋子,有其固定的坐标,如果需要区别所有的字符,就需要有足够的棋格容纳不同的“字符”。
英文和欧洲其他语言的单字节字符集(SingleByte Charsets):
首先对于ISO-8859系列的字符集都想象成一个:2^8 = 16 * 16 = 256个格子的棋盘,这样所有的西文字符(英文)用这样一个16×16的坐标系就基本可以覆盖全了。而英文实际上只用其中小于128(\x80)的部分就够了。利用大于128部分的空间的不同定义规则形成了真对其他欧洲语言的扩展字符集:ISO-8859-2 ISO-8859-4等……
ISO-8859-1 |
ISO-8859-7 |
其他语言 | ||||||||||||
|
|
|
GB2312 BIG5 SJIS等多字节字符集(MultiByte Charsets):
对于亚洲语言来说:汉字这么多,用这么一个256格的小棋盘肯定放不下,所以要区别成千上万的汉字解决办法就是用2个字节(坐标)来定位一个“字”在棋盘上的位置,将以上规则做一个扩展:
其结果相当于在位于128以上的小棋格里每个小棋格又划分出了一个16×16的小棋盘。这样一个棋盘中的格子数(可能容纳的字符数)就变成了128 + 128 * 256。按照类似的方式有了简体中文的GB2312标准,繁体中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大约有六仟多个常用简体汉字。
简体中文 |
日文SJIS |
繁体中文 | ||||||||||||||||||||||||||||||
|
|
|
由此可以看出,所有这些从ASCII扩展式的编码方式中:英文部分都是兼容的,但扩展部分的编码方式是不兼容的,虽然很多字在3种体系中写法一致(比如“中文”这2个字)但在相应字符集中的坐标不一致,所以GB2312编写的页面用BIG5看就变得面目全非了。而且有时候经常在浏览其他非英语国家的页面时(比如包含有德语的人名时)经常出现奇怪的汉字,其实就是扩展位的编码冲突造成的。
我把GBK和GB18030理解成一个小UNICODE:GBK字符集是GB2312的扩展(K),GBK里大约有贰万玖仟多个字符,除了保持和GB2312兼容外,繁体中文字,甚至连日文的假名字符也能显示。而GB18030-2000则是一个更复杂的字符集,采用变长字节的编码方式,能够支持更多的字符。关于汉字的编码方式比较详细的定义规范可以参考:
http://www.unihan.com.cn/cjk/ana17.htm
ASCII(英文) ==> 西欧文字 ==> 东欧字符集(俄文,希腊语等) ==> 东亚字符集(GB2312 BIG5 SJIS等)==> 扩展字符集GBK GB18030这个发展过程基本上也反映了字符集标准的发展过程,但这么随着时间的推移,尤其是互联网让跨语言的信息的交互变得越来越多的时候,太多多针对本地语言的编码标准的出现导致一个应用程序的国际化变得成本非常高。尤其是你要编写一个同时包含法文和简体中文的文档,这时候一般都会想到要是用一个通用的字符集能够显示所有语言的所有文字就好了,而且这样做应用也能够比较方便的国际化,为了达到这个目标,即使应用牺牲一些空间和程序效率也是非常值得的。UNICODE就是这样一个通用的解决方案。
UNICODE双字节字符集
所以你可以把UNICODE想象成这样:让所有的字符(包括英文)都用2个字节(2个8位)表示,这样就有了一个2^(8*2) = 256 * 256 = 65536个格子的大棋盘。在这个棋盘中,这样中(简繁)日韩(还包括越南)文字作为CJK字符集都放在一定的区位内,为了减少重复,各种语言中写法一样的字共享一个“棋格”。详细的区位见附录A
Unicode:(DoubleByte Charsets)
西 |
C中 |
|
其 |
欧 |
J日 |
|
它 |
英 |
K韩 |
|
语 |
文 |
|
|
言 |
什么还要有UTF-8?毕竟互联网70%以上的信息仍然是英文。如果连英文都用2个字节存取(UCS-2),空间浪费不就太多了?所谓UTF-8就是这样一个为了提高英文存取效率的字符集转换格式:Unicode Transformation Form 8-bit form。用UTF-8,UNICODE的2字节字符用变长个(1-3个字节)表示:
因此,在应用中程序处理过程中所有字符都是16位(双字节),但在存取转换成字节流时使用UTF-8格式转换,对于英文字符来说和原来用ASCII方式存取时相比大小仍然是一样的,而对中文来说和原来的GB2312编码方式相比,大小为:(3字节/2字节)=1.5倍
小节:
假设英文字符集是一个16×16的棋盘,么其他语言的字符集就是把高位区重新分割的(> 128)的中等棋盘,多种字符集之间互不兼容而UNICODE本身就相当于一个256×256的大棋盘,通过一定规则将英文和其他所有语言的字符都包含在内。
试验1:操作系统语言环境设置对Java应用缺省编码方式的影响
为了了解Java应用的编码处理的机制,首先要了解操作系统对JVM缺省编码方式的影响,因此我做了一个Env.java,用于打印显示不同系统下JVM的属性和系统支持的LOCALE。程序很简单:
/*
* Copyright (c) 2002 Email: chedongATbigfoot.com/chedongATchedong.com
* $Id: hello_unicode.html,v 1.6 2003/11/09 07:57:11 chedong Exp $
*/
import java.util.*;
import java.text.*;
/**
* 目的:
* 显示环境变量和JVM的缺省属性
* 输入:无
* 输出:
* 1 支持的LOCALE
* 2 JVM的缺省属性
*/
public class Env {
/**
* main entrance
*/
public static void main(String[] args) {
System.out.println("Hello, it's: " + new Date());
//print available locales
Locale list[] = DateFormat.getAvailableLocales();
System.out.println("======System available locales:======== ");
for (int i = 0; i < list.length; i++) {
System.out.println(list[i].toString() + "\t" + list[i].getDisplayName());
}
//print JVM default properties
System.out.println("======System property======== ");
System.getProperties().list(System.out);
}
}
最需要注意的是JVM的file.encoding属性,这个属性确定了JVM的缺省的编码/解码方式:从而影响应用中所有字节流==>字符流的解码方式 ,字符流==>字节流的编码方式。
LINUX下的LOCALE可以通过 LANG=zh_CN; LC_ALL=zh_CN.GBK; export LANG LC_ALL 设置。locale 命令可以显示系统当前的环境设置
Windows的LOCALE可以通过 控制面板==>区域设置 设置实现
GNU/Linux 2.4.x (J2SE1.3.1) |
GNU/Linux 2.4.x (J2SE1.3.1) |
Windows 2000(J2SE1.3.0) |
Windows 2000(J2SE1.3.0) |
Hello, it's: Tue Jul 30 11:05:44 CST 2002 |
Hello, it's: Tue Jul 30 11:07:34 CST 2002 |
Hello, it's: Tue Jul 30 11:49:36 CST 2002 |
Hello, it's: Tue Jul 30 11:53:27 CST 2002 |
结论1:
JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关。所以当设置成相同的LOCALE时,Linux和Windows下的缺省编码方式是没有区别的(可以认为cp1252=ISO-8859-1都是一样的西文编码方式,只包含255以下的拉丁字符),因此后面的测试2我只列出了GNU/Linux下LOCALE分别设置成zh_CN和en_US的测试结果输出。以下测试如果在Windows下分别按照不同的区域和字符集设置后试验的输出是一样的。
通过这个HelloUnicode.java程序,演示说明"Hello world 世界你好"这个字符串(16个字符)在不同缺省系统编码方式下的处理效果。在编码/解码的每个步骤之后,都打印出了相应字符串每个字符(Charactor)的byte值,short值和所在的UNICODE区间。
LANG=en_US LC_ALL=en_US |
LANG=zh_CN LC_ALL=zh_CN.GBK |
========testing1: write hello world to files======== (因为当前的英文环境下系统对于255以上的字符是不知道用什么字符表示的,因此全部用?显示) 但从相应的UNICODE MAPPING和SHORT值我们可以知道字符是正确的中文 但下一步的写入第2个文件html.gb2312.html, 没有指定编码方式(按系统缺省的ISO-8859-1编码方式), 因此从后面的测试2-2读取的结果是真的'?'了 [test 1-3]: convert string to UTF8string=Hello world 涓栫晫浣犲ソ length=24 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='? byte=-28 \uFFFFFFE4 short=228 \uE4 LATIN_1_SUPPLEMENT char[13]='? byte=-72 \uFFFFFFB8 short=184 \uB8 LATIN_1_SUPPLEMENT char[14]='? byte=-106 \uFFFFFF96 short=150 \u96 LATIN_1_SUPPLEMENT char[15]='? byte=-25 \uFFFFFFE7 short=231 \uE7 LATIN_1_SUPPLEMENT char[16]='? byte=-107 \uFFFFFF95 short=149 \u95 LATIN_1_SUPPLEMENT char[17]='? byte=-116 \uFFFFFF8C short=140 \u8C LATIN_1_SUPPLEMENT char[18]='? byte=-28 \uFFFFFFE4 short=228 \uE4 LATIN_1_SUPPLEMENT char[19]='? byte=-67 \uFFFFFFBD short=189 \uBD LATIN_1_SUPPLEMENT char[20]='? byte=-96 \uFFFFFFA0 short=160 \uA0 LATIN_1_SUPPLEMENT char[21]='? byte=-27 \uFFFFFFE5 short=229 \uE5 LATIN_1_SUPPLEMENT char[22]='? byte=-91 \uFFFFFFA5 short=165 \uA5 LATIN_1_SUPPLEMENT char[23]='? byte=-67 \uFFFFFFBD short=189 \uBD LATIN_1_SUPPLEMENT 第3个试验,将字符流按照UTF8方式编码后,写入第3个测试文件hello.utf8.html, 我们可以看到UTF8对英文没有影响,但对于其他文字使用了3字节编码方式, 因此比GB2312编码方式的存储要大50%, ========Testing2: reading and decoding from files======== [test 2-1]: read hello.orig.html: decoding with system default encoding string=Hello world 世界你好 length=20 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='? byte=-54 \uFFFFFFCA short=202 \uCA LATIN_1_SUPPLEMENT char[13]='? byte=-64 \uFFFFFFC0 short=192 \uC0 LATIN_1_SUPPLEMENT char[14]='? byte=-67 \uFFFFFFBD short=189 \uBD LATIN_1_SUPPLEMENT char[15]='? byte=-25 \uFFFFFFE7 short=231 \uE7 LATIN_1_SUPPLEMENT char[16]='? byte=-60 \uFFFFFFC4 short=196 \uC4 LATIN_1_SUPPLEMENT char[17]='? byte=-29 \uFFFFFFE3 short=227 \uE3 LATIN_1_SUPPLEMENT char[18]='? byte=-70 \uFFFFFFBA short=186 \uBA LATIN_1_SUPPLEMENT char[19]='? byte=-61 \uFFFFFFC3 short=195 \uC3 LATIN_1_SUPPLEMENT 按系统从中间存储hello.orig.html文件中读取相应文件, 虽然是按字节方式(半个“字”)读取的,但由于能完整的还原,因此输出显示没有错误。 其实PHP等应用很少出现字符集问题其实就是这个原因,全程都是按字节流方式处理, 很好的还原了输入,但这样处理的同时也失去了对字符的控制 [test 2-2]: read hello.gb2312.html: decoding as GB2312 string=Hello world ???? length=16 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='?' byte=63 \u3F short=63 \u3F BASIC_LATIN char[13]='?' byte=63 \u3F short=63 \u3F BASIC_LATIN char[14]='?' byte=63 \u3F short=63 \u3F BASIC_LATIN char[15]='?' byte=63 \u3F short=63 \u3F BASIC_LATIN 最惨的就是输出的时候这些'?'真的是问号char(63)了, 数据如果是这样就真的没救了 [test 2-3]: read hello.utf8.html: decoding as UTF8 string=Hello world ???? length=16 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='?' byte=22 \u16 short=19990 \u4E16 CJK_UNIFIED_IDEOGRAPHS char[13]='?' byte=76 \u4C short=30028 \u754C CJK_UNIFIED_IDEOGRAPHS char[14]='?' byte=96 \u60 short=20320 \u4F60 CJK_UNIFIED_IDEOGRAPHS char[15]='?' byte=125 \u7D short=22909 \u597D CJK_UNIFIED_IDEOGRAPHS great! 字符虽然显示为'?',但实际上字符的解码是正确的, 从相应的UNICODE MAPPING就可以看的出来。 |
========Testing1: write hello world to files======== 这个输出其实都是把UNICODE按GBK字符集解码的效果。 ========Testing2: reading and decoding from files======== [test 2-1]: read hello.orig.html: decoding with system default encoding string=Hello world 世界你好 length=16 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='世' byte=22 \u16 short=19990 \u4E16 CJK_UNIFIED_IDEOGRAPHS char[13]='界' byte=76 \u4C short=30028 \u754C CJK_UNIFIED_IDEOGRAPHS char[14]='你' byte=96 \u60 short=20320 \u4F60 CJK_UNIFIED_IDEOGRAPHS char[15]='好' byte=125 \u7D short=22909 \u597D CJK_UNIFIED_IDEOGRAPHS [test 2-2]: read hello.gb2312.html: decoding as GB2312 string=Hello world 世界你好 length=16 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='世' byte=22 \u16 short=19990 \u4E16 CJK_UNIFIED_IDEOGRAPHS char[13]='界' byte=76 \u4C short=30028 \u754C CJK_UNIFIED_IDEOGRAPHS char[14]='你' byte=96 \u60 short=20320 \u4F60 CJK_UNIFIED_IDEOGRAPHS char[15]='好' byte=125 \u7D short=22909 \u597D CJK_UNIFIED_IDEOGRAPHS [test 2-3]: read hello.utf8.html: decoding as UTF8 string=Hello world 世界你好 length=16 char[0]='H' byte=72 \u48 short=72 \u48 BASIC_LATIN char[1]='e' byte=101 \u65 short=101 \u65 BASIC_LATIN char[2]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[3]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[4]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[5]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[6]='w' byte=119 \u77 short=119 \u77 BASIC_LATIN char[7]='o' byte=111 \u6F short=111 \u6F BASIC_LATIN char[8]='r' byte=114 \u72 short=114 \u72 BASIC_LATIN char[9]='l' byte=108 \u6C short=108 \u6C BASIC_LATIN char[10]='d' byte=100 \u64 short=100 \u64 BASIC_LATIN char[11]=' ' byte=32 \u20 short=32 \u20 BASIC_LATIN char[12]='世' byte=22 \u16 short=19990 \u4E16 CJK_UNIFIED_IDEOGRAPHS char[13]='界' byte=76 \u4C short=30028 \u754C CJK_UNIFIED_IDEOGRAPHS char[14]='你' byte=96 \u60 short=20320 \u4F60 CJK_UNIFIED_IDEOGRAPHS char[15]='好' byte=125 \u7D short=22909 \u597D CJK_UNIFIED_IDEOGRAPHS 结论:如果后台数据采用UNICODE方式的存储 然后根据需要指定字符集编码、解码方式,则应用几乎可以不受前端应用所处 环境字符集设置的影响 |
试验2的一些结论:
试验3:WEB应用中的输入输出中的编码问题:Java是为做国际化应用设计的,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字符集解码/编码的具体定义请参考:
以前能够配置让一个WEB应用能够在GBK方式编码的中文Windows2000服务器上和按ISO-8859-1方式编码的GNU/Linux上都能够正确的显示中文一直让我迷惑了很久。我仔细想了一下,后来终于想明白了,在一个国际化的应用中:ServletRequest和ServletResponse的编码/解码方式的确不应该根据服务器设置成固定的字符集,而应该是面向客户端语言环境进行输入/输出编码方式的自适应。一个按照国际化规范设计的WEB应用中:
在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试验程序得出的一些结论:
根据以上结论,设计一个适应多语言环境的应用,可以考虑一下2个应用处理模式:
(服务器端或跨语言平台应用):在应用的最外端:数据输入输出判断用户语言环境,核心按照UNICODE方式处理存储。可以把各种区域性的字符集(GB2312 BIG5)看成是UNICODE的一个子集。UNICODE存储的数据可以方便的转换成任意字符集。
应用使用UTF8方式存储虽然要增加了存储空间,但也可以大大简化前端应用本地化(i10n)的复杂程度。
简体中文输入 繁体中文输入 简体中文输出 繁体中文输出
\ / \ /
判断用户语言环境:解码 判断用户语言环境:编码
\ /
中间处理过程:UNICODE
|
UTF8编码存储
随着UNICODE被愈来愈多的系统和平台支持:Python Perl Glibc等,但我们应该珍惜一开始就按照国际化规范设计Java,并将其和新发展起来的XML规范相配合,相信符合国际化规范的应用设计从长远来看会展现出更多的优势。
TODO:
数据库应用中的字符集问题试验:MySQL Oracle JD
A. The Unicode 2.0 Character Set
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 |
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者