字符集编码ANSI和UNICODE
编码指不同国家的语言在计算机中的一种存储和解释规范
ANSI与ASCII
n 最初,Internet上只有一种字符集——ANSI的ASCII字符集(American Standard Code for Information Interchange, “美国信息交换标准码),它使用7 bits来表示一个字符,总共表示128个字符,后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符,充分利用了一个字节所能表达的最大信息
nANSI字符集:ASCII字符集,以及由此派生并兼容的字符集,如:GB2312,正式的名称为MBCS(Multi-Byte Chactacter System,多字节字符系统),通常也称为ANSI字符集。
UNICODE与UTF8,UTF16
n由于每种语言都制定了自己的字符集,导致最后存在的各种字符集实在太多,在国际交流中要经常转换字符集非常不便。因此,产生了Unicode字符集,它固定使用16 bits(两个字节)来表示一个字符,共可以表示65536个字符
n 标准的Unicode称为UTF-16(UTF:UCS Transformation Format )。后来为了双字节的Unicode能够在现存的处理单字节的系统上正确传输,出现了UTF-8,使用类似MBCS的方式对Unicode进行编码。 (Unicode字符集有多种编码形式)
例如“连通”两个字的Unicode标准编码UTF-16 (big endian)为:DE 8F 1A 90
而其UTF-8编码为:E8 BF 9E E9 80 9A
n当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码:
检测文件头标识,提示用户选择,根据一定的规则猜测
最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding,如下表:
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.
*********************************
CodePage的作用
作者: 更新时间:2005-12-13 17:45:49 浏览次数:31
从定义中我们可以看出,CodePage的作用,是决定页面以何种编码方式显示动态内容。当页面被服务器处理之后,页面将以CodePage设定的编码输出到客户端。当然,CodePage的参数需正确,否则,将产生错误信息“CodePage 值无效。指定的 CodePage 值无效。”(事件ID: 0204)。如果CodePage没有设置,则服务器使用默认的CodePage加载到你的Session里面,使用
程序代码:
Response.Write(Session.CodePage)
可以查看你当前使用的CodePage。
再谈一下字符集Charset。当打开一个网页的时候,浏览器在meta里面找到Charset,并自动选择对应的字符集来显示页面。页面的数据服务器已经使用CodePage的编码发送到了客户端,如果浏览器设定的Charset和CodePage不对应,则浏览器通常不能看到正确的显示结果。
知道了以上两点,我们在ASP页面中设置的字符集与CodePage对应,则可以让页面直接显示正常结果,否则,则可能需要用户手动调整浏览器设置才能够正常浏览页面。我们常见的简体中文(GBK)的CodePage值为936,字符集名称为gb2312;UTP-8的CodePage值为65001,字符集名称为utf-8。
但是,仅凭以上的这些解决方法,并不能完全解决页面编码对ASP文件产生的问题。
我们再来了解一下ASP文件被浏览的过程。当文件A.asp首次被客户端请求时:
1、IIS调入该文件,读入该文件;
2、验证语法,然后执行处理程序,将结果返回客户端;
3、浏览器缓冲从服务器返回的HTML代码,解释执行,将结果显示出来。
刚才我们知道了上述的第2、3步,即服务器返回客户端,客户端显示的问题。那么在第1步中会不会出现编码问题呢?答案是肯定的。
我们目前使用的Windows操作系统,文本文件默认编码采用ANSI(American National Standards Institute)。既然大家都采用一种默认编码,这边相当于规范化了,于是出问题的时候少。但网络是国际化的,需要一种国际化的标准,目前这个标准是 UTF-8。实际上,对于我们使用的Windows系统,其编码比较复杂,系统内部采用ANSI+DBCS。(DBCS为双字节编码,支持东亚文字。)而对外服务,大致上是ANSI和Wide_Char混合,相当于UTF-8。UTF-8是一种方向,采用这种编码将是大势所趋。如果我们将asp文件上传到别的服务器,而你的文件中含有该服务器不能正确读取的编码,则服务器则根据该编码的长度以同样长度的默认字符填充。比如收到一封邮件,所有中文都是两个 "?",相信这种事情大家都遇到过。假如你的asp文件在本地测试是正常的,而上传到某个服务器,却发现文字显示不正常,排除浏览器设置的问题之后,你应该首先想到的服务器不支持你文件的编码。
也许这是你会问两个问题:
1、为什么不支持我文件的编码,却能够执行呢?
这个要感谢ASCII这个先驱,从0开始到7F至的128个编码都是遵循这个标准的。这已经含括了asp程序中的变量名、函数名、操作符、算子等,所以程序是能够被执行的,但是如果变量包括的这128以外的编码,就不一定能够找到对应的字符了。如果不能找到,系统将采用字符“?”替代。
************************************
UTF-8 字符集基础
作者: Marius Bancila 翻译: wangxg
原文参见:http://www.codeguru.com/Cpp/misc/misc/multi-lingualsupport/article.php/c10451/
字符集简史
在所有字符集中,最知名可能要数被称为ASCII的7位字符集了。它是美国信息交换标准委员会(American Standards Committee for Information Interchange)的缩写, 为美国英语通信所设计。它由128个字符组成,包括大小写字母、数字0-9、标点符号、非打印字符(换行符、制表符等4个)以及控制字符(退格、响铃等)组成。
但是,由于他是针对英语设计的,当处理带有音调标号(形如汉语的拼音)的欧洲文字时就会出现问题。因此,创建出了一些包括255个字符的由ASCII扩展的字符集。其中有一种通常被成为IBM字符集,它把值为128-255之间的字符用于画图和画线,以及一些特殊的欧洲字符。另一种8位字符集是ISO 8859-1 Latin 1,也简称为ISO Latin-1。它把位于128-255之间的字符用于拉丁字母表中特殊语言字符的编码,也因此而得名。
欧洲语言不是地球上的唯一语言,因此亚洲和非洲语言并不能被8位字符集所支持。仅汉语(或pictograms)字母表就有80000以上个字符。但是把汉语、日语和越南语的一些相似的字符结合起来,在不同的语言里,使不同的字符代表不同的字,这样只用2个字节就可以编码地球上几乎所有地区的文字。因此,创建了UNICODE编码。它通过增加一个高字节对ISO Latin-1字符集进行扩展,当这些高字节位为0时,低字节就是ISO Latin-1字符。UNICODE支持欧洲、非洲、中东、亚洲(包括统一标准的东亚像形汉字和韩国像形文字)。但是,UNICODE并没有提供对诸如 Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau文字的支持。同时它也不支持如Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian之类的古老的文字。
事实证明,对可以用ASCII表示的字符使用UNICODE并不高效,因为UNICODE比ASCII占用大一倍的空间,而对ASCII来说高字节的0对他毫无用处。为了解决这个问题,就出现了一些中间格式的字符集,他们被称为通用转换格式,既UTF(Universal Transformation Format)。目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF-16, 以及 UTF-32。本文讨论UTF-8字符集的基础。
UTF_8字符集
UTF-8是UNICODE的一种变长字符编码,由Ken Thompson于1992年创建。现在已经标准化为RFC 3629。UTF-8用1到6个字节编码UNICODE字符。如果UNICODE字符由2个字节表示,则编码成UTF-8很可能需要3个字节,而如果 UNICODE字符由4个字节表示,则编码成UTF-8可能需要6个字节。用4个或6个字节去编码一个UNICODE字符可能太多了,但很少会遇到那样的 UNICODE字符。
UFT-8转换表表示如下:
UNICODE UTF-8
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
实际表示ASCII字符的UNICODE字符,将会编码成1个字节,并且UTF-8表示与ASCII字符表示是一样的。所有其他的UNCODE字符转化成 UTF-8将需要至少2个字节。每个字节由一个换码序列开始。第一个字节由唯一的换码序列,由n位1加一位0组成。n位1表示字符编码所需的字节数。
示例
UNICODE uCA(11001010) 编码成UTF-8将需要2个字节:
uCA -> C3 8A
1100 1010
110xxxxx 10xxxxxx
1100 1010 -> 110xxxxx 10xxxxxx
-> 110xxxxx 10xxxxx0
-> 110xxxxx 10xxxx10
-> 110xxxxx 10xxx010
-> 110xxxxx 10xx1010
-> 110xxxxx 10x01010
-> 110xxxxx 10001010
-> 110xxxx1 10001010
-> 110xxx11 10001010
-> 11000011 10001010
-> C3 8A
UNICODE uF03F (11110000 00111111) 编码成UTF-8将需要3个字节:
u F03F -> EF 80 BF
1111 0000 0011 1111 -> 1110xxxx 10xxxxxx 10xxxxxx
-> 11101111 10000000 10111111
-> EF 80 BF
译者注:由上分析可以看到,UNCODE到UTF-8的转换就是先确定编码所需要的字节数,然后用UNICODE编码位从低位到高位依次填入上面表示为x的位上,不足的高位以0补充。以上是个人经验,如有错误,请不惜指教,谢过先:)
UTF-8编码的优点:
UTF-8编码可以通过屏蔽位和移位操作快速读写。
字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易。
字节FF和FE在UTF-8编码中永远不会出现,因此他们可以用来表明UTF-16或UTF-32文本(见BOM)
UTF-8 是字节顺序无关的。它的字节顺序在所有系统中都是一样的,因此它实际上并不需要BOM。
UTF-8编码的缺点:
你无法从UNICODE字符数判断出UTF-8文本的字节数,因为UTF-8是一种变长编码
它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符
ISO Latin-1 是UNICODE的子集(子集的说法并不等于兼容,88591是unicode的子集,只是说高字节为0的时候低字节就表示88591,但是如果数据流中有一串88591代码时,在以unicode解码时并不能将其正确捡出,坦率的说这本就不应该比较,因为8859系列和DBCS编码方式是一套字符集,但是Unicode只是规定了编码,并没有规定如保存和传输。表示一个Unicode可以有多种方式,如Utf系列(即通用转换格式),实际上说8859系列与纯粹的Ascii(7位)也不兼容,但是现在的Ascii系统都是以8位存储的,所以也就没什么关系,实际上说现在指的Ascii码是8*8码集的一半而矣,现在很少以线粹的7位的Ascii码形式存储数据流),但不是UTF-8的子集
8位字符的UTF-8编码会被email网关过滤,因为internet信息最初设计为7为ASCII码。因此产生了UTF-7编码。
UTF-8 在它的表示中使用值100xxxxx的几率超过50%, 而现存的实现如ISO 2022, 4873, 6429, 和8859系统,会把它错认为是C1 控制码。因此产生了UTF-7.5编码。
修正的UTF-8:
java使用UTF-16表示内部文本,并支持用于字符串串行化的非标准的修正UTF-8编码。标准UTF-8和修正的UTF-8有两点不同:
修正的UTF-8中,null字符编码成2个字节(11000000 00000000)而不是标准的1个字节(00000000),这样作可以保证编码后的字符串中不会嵌入null字符。因此如果在类C语言中处理字符串,文本不会在第一个 null字符时截断(C字符串以null结尾)。
在标准UTF-8编码中,超出基本多语言范围(BMP - Basic Multilingual Plain)的字符被编码为4字节格式,但是在修正的UTF-8编码中,他们由代理编码对(surrogate pairs)表示,然后这些代理编码对在序列中分别重新编码。结果标准UTF-8编码中需要4个字节的字符,在修正后的UTF-8编码中将需要6个字节。
位序标志BOM
BOM(Byte Order Mark)是一个字符,它表明UNICODE文本的UTF-16,UTF-32的编码字节顺序(高字节低字节顺序)和编码方式(UTF-8,UTF-16,UTF-32, 其中UTF-8编码是字节顺序无关的)。
如下所示:
Encoding Representation
UTF-8 EF BB BF
UTF-16 Big Endian FE FF
UTF-16 Little Endian FF FE
UTF-32 Big Endian 00 00 FE FF
UTF-32 Little Endian FF FE 00 00
****************************************
以下?热菔???Google搜?ざ??恚?
按????UTF-8?n案加不加BOM都可以(?????Y料可?g?[www.unicode.org),因?槌淌揭话愣伎梢砸牢恼??热萃扑闶欠?UTF-8,不?^以 UltraEdit?槔?,有?rUTF-8短句是??被?e?J??ANSI???a。加BOM是?^保?U的做法,而且MS的??事本、IE及Word等??文件?Τ?UTF- 8格式?r都是加入BOM的,??程式?_???r能?p易辨明正?_???a。
UTF-8的BOM是 EFBBBF,因??UltraEdit?R入UTF-8文件後?热????D做Unicode-LE(所以在Hex模式看到的?K非UTF-8文件原本的字?a),上述的EFBBBF???D成Unicode-LE後便是FFFE(Unicode-LE的BOM),UltraEdit不能辨?J?@??BOM有理?o理自己又加多一??BOM,所以有????FFFE出?F。
因此,最好的方法就是在UltraEdit????
Write UTF-8 BOM header to ALL UTF-8 files when saved
?O成OFF,就不??在?Υ??r又在?n案中加入了BOM
**************************
谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词
问题一:
使用Windows记事本的“另存为”,可以在GBK、Unicode、Unicode big endian和UTF-8这几种编码方式间相互转换。同样是txt文件,Windows是怎样识别编码方式的呢?
我很早前就发现Unicode、Unicode big endian和UTF-8编码的txt文件的开头会多出几个字节,分别是FF、FE(Unicode),FE、FF(Unicode big endian),EF、BB、BF(UTF-8)。但这些标记是基于什么标准呢?
问题二:
最近在网上看到一个ConvertUTF.c,实现了UTF-32、UTF-16和UTF-8这三种编码方式的相互转换。对于Unicode (UCS2)、 GBK、UTF-8这些编码方式,我原来就了解。但这个程序让我有些糊涂,想不起来UTF-16和UCS2有什么关系。
查了查相关资料,总算将这些问题弄清楚了,顺带也了解了一些Unicode的细节。写成一篇文章,送给有过类似疑问的朋友。本文在写作时尽量做到通俗易懂,但要求读者知道什么是字节,什么是十六进制。
0、big endian和little endian
big endian和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。如果将49写在前面,就是little endian。
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
1、字符编码、内码,顺带介绍汉字编码
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码,为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。
GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。
从ASCII、GB2312到GBK,这些编码方法是向下兼容的,即同一个字符在这些方案中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK都属于双字节字符集 (DBCS)。
2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。从汉字字汇上说,GB18030在GB13000.1的20902个汉字的基础上增加了CJK扩展A的6582个汉字(Unicode码 0x3400-0x4db5),一共收录了27484个汉字。
CJK就是中日韩的意思。Unicode为了节省码位,将中日韩三国语言中的文字统一编码。GB13000.1就是ISO/IEC 10646-1的中文版,相当于Unicode 1.1。
GB18030的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节编码的码位就是收录了CJK扩展A的6582个汉字。例如:UCS的0x3400在GB18030中的编码应该是8139EF30,UCS的0x3401在GB18030中的编码应该是8139EF31。
微软提供了GB18030的升级包,但这个升级包只是提供了一套支持CJK扩展A的6582个汉字的新字体:新宋体-18030,并不改变内码。Windows 的内码仍然是GBK。
这里还有一些细节:
*HPLaserJ
GB2312的原文还是区位码,从区位码到内码,需要在高字节和低字节上分别加上A0。
*
对于任何字符编码,编码单元的顺序是由编码方案指定的,与endian无关。例如GBK的编码单元是字节,用两个字节表示一个汉字。这两个字节的顺序是固定的,不受CPU字节序的影响。UTF-16的编码单元是word(双字节),word之间的顺序是编码方案指定的,word内部的字节排列才会受到endian的影响。后面还会介绍UTF-16。
*
GB2312 的两个字节的最高位都是1。但符合这个条件的码位只有128*128=16384个。所以GBK和GB18030的低字节最高位都可能不是1。不过这不影响DBCS字符流的解析:在读取DBCS字符流时,只要遇到高位为1的字节,就可以将下两个字节作为一个双字节编码,而不用管低字节的高位是什么。
2、Unicode、UCS和UTF
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容(更准确地说,是与ISO-8859-1兼容),与GB码不兼容。例如“汉”字的Unicode编码是6C49,而GB码是BABA。
Unicode也是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"Unicode Character Set"的缩写。
根据维基百科全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试图独立设计Unicode的组织,即国际标准化组织(ISO)和一个软件制造商的协会(unicode.org)。ISO开发了ISO 10646项目,Unicode协会开发了Unicode项目。
在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO 10646-1相同的字库和字码。
目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode 4.1.0。ISO的最新标准是ISO 10646-3:2003。
UCS只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49,我可以用4个ascii数字来传输、保存这个编码;也可以用utf-8编码:3个连续的字节E6 B1 89来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的好处是它与ISO- 8859-1完全兼容。UTF是“UCS Transformation Format”的缩写。
IETF的RFC2781和RFC3629以RFC的一贯风格,清晰、明快又不失严谨地描述了UTF-16和UTF-8的编码方法。我总是记不得IETF 是Internet Engineering Task Force的缩写。但IETF负责维护的RFC是Internet上一切规范的基础。
2.1、内码和code page
目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的编码,而全部改用Unicode。
Windows使用代码页(code page)来适应各个国家和地区。code page可以被理解为前面提到的内码。GBK对应的code page是CP936。
微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。
3、UCS-2、UCS-4、BMP
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。下面让我们做一些简单的数学游戏:
UCS-2有2^16=65536个码位,UCS-4有2^31=2147483648个码位。
UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行 (rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。
4、UTF编码
UTF-8就是以8位为单元对UCS进行编码。从UCS-2到UTF-8的编码方式如下:
UCS-2编码(16进制) UTF-8 字节流(二进制)
0000 - 007F 0xxxxxxx
0080 - 07FF 110xxxxx 10xxxxxx
0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。
读者可以用记事本测试一下我们的编码是否正确。需要注意,UltraEdit在打开utf-8编码的文本文件时会自动转换为UTF-16,可能产生混淆。你可以在设置中关掉这个选项。更好的工具是Hex Workshop。
UTF-16以16位为单元对UCS进行编码。对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于 0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为 UTF-16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题。
5、UTF的字节序和BOM
UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎” 还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不应该出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。
这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。
UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF(读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。
Windows就是使用BOM来标记文本文件的编码方式的。
“GB2312的原文”是指国家1980年的一个标准《中华人民共和国国家标准 信息交换用汉字编码字符集 基本集 GB 2312-80》。这个标准用两个数来编码汉字和中文符号。第一个数称为“区”,第二个数称为“位”。所以也称为区位码。1-9区是中文符号,16-55 区是一级汉字,56-87区是二级汉字。现在Windows也还有区位输入法,例如输入1601得到“啊”。(这个区位输入法可以自动识别16进制的 GB2312和10进制的区位码,也就是说输入B0A1同样会得到“啊”。)
内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。现在的Windows在系统内部支持Unicode,然后用代码页适应各种语言,“内码”的概念就比较模糊了。微软一般将缺省代码页指定的编码说成是内码。
内码这个词汇,并没有什么官方的定义,代码页也只是微软这个公司的叫法。作为程序员,我们只要知道它们是什么东西,没有必要过多地考证这些名词。
所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。
Windows中有缺省代码页的概念,即缺省用什么编码来解释字符。例如Windows的记事本打开了一个文本文件,里面的内容是字节流:BA、BA、D7、D6。Windows应该去怎么解释它呢?
是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释,还是按照ISO8859-1去解释?如果按GBK去解释,就会得到“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错误的字符。所谓“错误”是指与文本作者的本意不符,这时就产生了乱码。
答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI,其实就是按照缺省代码页的编码方法保存。
Windows的内码是Unicode,它在技术上可以同时支持多个代码页。只要文件能说明自己使用什么编码,用户又安装了对应的代码页,Windows就能正确显示,例如在HTML文件中就可以指定charset。
有的HTML文件作者,特别是英文作者,认为世界上所有人都使用英文,在文件中不指定charset。如果他使用了0x80-0xff之间的字符,中文 Windows又按照缺省的GBK去解释,就会出现乱码。这时只要在这个html文件中加上指定charset的语句,例如:
如果原作者使用的代码页和ISO8859-1兼容,就不会出现乱码了。
再说区位码,啊的区位码是1601,写成16进制是0x10,0x01。这和计算机广泛使用的ASCII编码冲突。为了兼容00-7f的ASCII 编码,我们在区位码的高、低字节上分别加上A0。这样“啊”的编码就成为B0A1。我们将加过两个A0的编码也称为GB2312编码,虽然GB2312的原文根本没提到这一点。
************************************
Java 繁体中文处理完全攻略 -- 简体中文参照操作即可
yuannaodai 转贴 (参与分:5580,专家分:476) 发表:2005-08-08 23:27 版本:1.0 阅读:1215次
Java 繁体中文处理完全攻略
许多人用 Java 处理到中文资料时,常会出现乱码。关于 Java 和中文兼容性的问题,实在让许多程序员为此伤透脑筋,相关的问题每隔几天就会出现在网络上。为了舒缓您紧蹙的眉头,我特别写了这系列文章,解说 Java 牵涉到文字时的内部处理方式,供读者参考。读完本系列文章之后,不求甚解者可以治标,充分理解者可以治本。本文贵在原理解说,别光是囫囵吞枣。
快速解决之道
如果你目前正遭遇到 Java 和中文不兼容的问题,请你注意下面这几点,说不定问题能马上迎刃而解:
1. 检查操作系统设定:先检查你的操作系统,确定国籍语言资料是「Traditional Chinese(Taiwan)」。国籍语言资料的设定会影响 Java 编译器与JRE的判断。我之前就是因为国籍资料设定不正确,出了一堆 Java 和中文不兼容的怪事。
2. 更新 Java 环境版本:改用最新版的 JDK,新版本的 JDK 说不定已经解决你原有的问题。请注意:某些 Java IDE 所用的编译器和 JRE 是不兼容于中文的(我遇过这样的情形),你最好能把 Java IDE 的 JDK 指到新版的 JDK。另外,如果数据库取回的资料是乱码,换别套或者更新 JDBC 驱动程序试试看。
如果还是无法解决,请详细阅读下面各小节的内容,仔细推敲你的错误所在。
Unicode、UTF-16、UTF-8
Java 内部处理字符使用的字序方式是 Unicode,这是一种通行全球的编码方式。Unicode 因为必须将中、韩、日、英、法、阿拉伯……等许多国家所使用的文字都纳入,目前已经包含了六万多个字符,所以 Unicode 使用了 16 个位来为字符编码。因为 Unicode 使用了 16 位编码,所以每个字符都用 16 位来储存或传输是很自然的事,这种储存或传输的格式称为 UTF-16(是不是很像战斗机的名字)。如果你使用到的字符都是西方字符,那么你一定不会想用 UTF-16 的格式,因为体积比 8 位的 Latin-1(一种扩充 ASCII 的编码)多了一倍。所以 Unicode 另有一种储存或传输的格式,叫做 UTF-8。UTF-8 的格式在编码英文时,只需要 8 位,但是中文则是 24 位,所以中文字出现比例高的地方还是使用 UTF-16 比较节省空间。Java 的 Class File(也就是 bytecode)中有一字段叫做常数区(Constant Pool),一律使用 UTF-8 为字符编码。
关于 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出版)。
Unicode 与繁体中文编码的互转
虽然 Java 内部完整地使用 Unicode,但是你所使用的操作系统可不见得。以繁体中文版的 Windows 98 来说,预设的编码方式是 MS950,这是一种兼容于 Big 5的编码方式。字符串数据从 Windows 一送进 JRE,JRE 的转码系统马上先把字符串编码由 MS950 转成 Unicode,才能进行处理。字符串资料由 JRE 一送出给 Windows,JRE 的转码系统马上先将其由 Unicode 转成 MS950,操作系统才能处理。
想知道你的 JDK 或 JRE 会用什么样的编码方式来和操作系统沟通,请执行下面的 Java 程序:
public class ShowNativeEncoding {
public static void main(String[] args) {
String enc = System.getProperty("file.encoding");
System.out.println(enc);
}
}
I/O 转码
Java 现行的 IO 一律使用 Stream 的方式,相关的类别都放在 java.io 中。输出 binary 的资料使用 OutputStream 的子类别,输入 binary 的资料使用 InputStream 的子类别,输出文字的资料使用 Writer 的子类别,输入文字的资料使用 Reader 的子类别。
你可能会觉得很奇怪:「有必要用不同的方式来处理文字和 binary 吗?文字资料不也是 binary 的一种?」没错,其实他们非常类似,最大的差异在于,InputStream/OutputStream 会原封不动地传送资料,但是 Reader/Writer 会将资料当作文字对待,所以 Reader/Writer 在「必要时」会把(文字)资料转码。什么时候才是所谓的「必要时」呢?
Java 的 Stream(包括 Reader 和 Writer)是可以互相串接的。当 Reader 的资料来源是另一个 Reader 时,不转码,当 Reader 的资料来源是一个 InputStream 时,就会转码。当 Writer 的资料去处是另一个 Writer 时,不转码,当 Writer 的资料去处是一个 OutputStream 时,就会转码。
由什么码转成什么码?这是可以指定的。因为转码只发生在 Reader/InputStream 的交界处与 Writer/OutputStream 的交界处,所以正是由 InputStreamReader 和 OutputStreamWriter 此二类别负责,下面两个 constructor 的第二个参数,正是用来指定转码的方式。
public InputStreamReader(InputStream in, String enc)
throws UnsupportedEncodingException;
public OutputStreamWriter(OutputStream out, String enc)
throws UnsupportedEncodingException;
InputStreamReader 负责将 enc 的编码方式转成 Unicode(因为资料是从「外部」送过来给「内部」的),OutputStreamWriter 负责将 Unicode 的编码方式转成 enc(因为资料要从「内部」送给「外部」)。JRE 内部当然都一定是用 Unicode 编码,而外部的编码就不一定,要看当时的环境为何。你可以透过 getEncoding() 的 method,来得知 InputStreamReader 与 OutputStreamWriter 的编码方式。
请注意:即使你没用到 InputStreamReader 与 OutputStreamWriter,只有用到其它的 Reader 和 Writer,但是这些 Reader 和 Writer 内部也很有可能(但非绝对)是直接或间接通到 InputStreamReader 与 OutputStreamWriter。比方说:FileReader 内部其实是透过一个 InputStreamReader 的中介来将资料从 FileInputStream 取过来的,此时 InputStreamReader 的转码方式是采用 OS 的文字编码(以繁体中文的 Windows 为例,就是「MS950」)转成 Unicode。
如果你清楚地知道你要读写的档案(或资料来源 / 去处)是采用某种编码方式,你也可以主动指定编码方式。但是,请记得抓取可能导致的 UnsupportedEncodingException,并务必处理之,不可对此例外置之不理,因为该 JRE 有可能没有附上此种编码表(也有可能你的编码名称给错)。
档案 I/O 转码
如果你是在泰文版的 Windows 上,想读取用 MS950 编码的繁体中文文字文件,你就必须主动指定编码,不可以直接用 FileReader,否则无法成功读取。方法如下:
FileInputStream fis = new FileInputStream(fileName);
InputStreamReader reader = new InputStreamReader(fis, "MS950");
然后,透过 Reader 读出来的就会是正确的中文。
网络 I/O 转码
如果你的网络程序采用 TCP,那么你可以透过 Socket 类别所提供的 getInputStream() 和 getOutputStream() 来得到 InputStream 和 OutputStream 对象。如果你是在泰文版的 Windows 上,想读取用 MS950 编码的繁体中文文字 TCP 网络串流,你可以用类似上面的技巧来转码。方法如下:
InputStream is = mySocket.getInputStream();
InputStreamReader reader = new InputStreamReader(is, "MS950");
如果你的网络程序采用 UDP,你必须把中文字符串转成(或转自)byte 数组。请看下一节「 字符串和 byte 数组的转码 」。
如果你的网络程序采用 RMI,那你完全不用为这部分的转码操心,字符串直接用 Unicode 在网络上传递给另一个 JRE,不需要转码。
保持刑案现场
如果你不知道你的 I/O 资料来源或去处是用何种编码方式,那么你最好不要用 Reader 和 Writer,而应该直接用 InputStream 和 OutputStream,因为与其被 Reader 和 Writer 胡乱编码之后造成信息遗失或错乱,不如保持资料的完整不变,留待以后进一步解读。
字符串和 byte 数组的转码
java.lang.String 类别是 Java 字符串对象的类别,Java 字符串对象既然是活在 JRE 内部,当然就一定是用 Unicode 编码。如果你需要将 String 对象和 byte 数组互转,你可以使用:
String(byte[] bytes, int offset, int length, String enc);
或
String(byte[] bytes, String enc);
来将用 enc 编码的 byte 数组,转成 Unicode 的 String 对象。你也可以使用 String 对象所提供的:
byte[] getBytes(String enc)
来将 String 对象转成 byte 数组。
另外,你也可以透过 ByteArrayInputStream 或 ByteArrayOutputStream 串接到 InputStreamReader 或 OutputStreamWriter,来达到转码的目的。
*************************
数字字元参引 (Numeric Character Reference, NCR)的概念是用iso10646(即Unicode)的码值表示多字节语种,这主要是要Xml规范中引入,现在主要的浏览器都支持它,形式如下:
汉字 汉字
posted on 2006-06-16 22:43
汪杰 阅读(1091)
评论(1) 编辑 收藏 引用