分类:
2010-08-23 18:59:15
最近遇到一个Unicode和UTF-8关系比较的问题,之前在处理中文显示时也遇到过类似的问题,于是花时间学习了一下,在此做个总结归纳,借以加深理解。(本文多数内容均来自互联网,特此申明。)
ISO组织制定的国际标准ISO 10646定义了通用字符集 (Universal Character Set, UCS)。UCS是所有其他字符集标准的一个超集,它保证与其他字符集是双向兼容的。就是说,如果你将任何文本字符串翻译到UCS格式,然后再翻译回原编码, 你不会丢失任何信息。
ISO 10646定义了一个31位的字符集。然而,在这巨大的编码空间中,迄今为止只分配了前 65534个码位 (0x0000 到 0xFFFD)。这个UCS的16位子集称为基本多语言面
(Basic Multilingual Plane, BMP)。将被编码在16位BMP以外的字符都属于非常特殊的字符(比如象形文字),且只有专家在历史和科学领域里才会用到它们。
UCS不仅给每个字符分配一个代码,而且赋予了一个正式的名字。表示一个UCS或Unicode 值的十六进制数,通常在前面加上"U+",就象U+0041代表字符"拉丁大写字母A"。UCS字符U+0000到U+007F与US-ASCII(ISO
646)是一致的,U+0000到U+00FF与ISO
8859-1(Latin-1) 也是一致的。从U+E000到U+F8FF,以及BMP以外的大范围的编码是为私用保留的。
UCS-2,用2个byte表示一个字符,即可以表示BMP内的全部字符。
UCS-4,用4个byte表示一个字符(实际上只用了31位,最高位必须为0),可以表示所有UCS字符(0~0x10FFFF)。
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之外。
Unicode协会制定的编码机制,要将全世界常用文字都函括进去。在1.0中是16位编码, 由U+0000到U+FFFF。在2.0开始抛弃了16位限制,原来的16位作为基本位平面(BMP),另外增加了16个位平面,相当于20位编码,编码范围0到0x10FFFF。
1)UTF-8就是以8bit为单元对UCS/Unicode进行编码。从Unicode到UTF-8编码方式如下。
Unicode |
UTF-8 |
U-00000000 - U-0000007F: |
0xxxxxxx |
U-00000080 - U-000007FF: |
110xxxxx 10xxxxxx |
U-00000800 - U-0000FFFF: |
1110xxxx 10xxxxxx 10xxxxxx |
U-00010000 - U-001FFFFF: |
11110xxx 10xxxxxx 10xxxxxx
10xxxxxx |
U-00200000 - U-03FFFFFF: |
111110xx 10xxxxxx 10xxxxxx
10xxxxxx 10xxxxxx |
U-04000000 - U-7FFFFFFF: |
1111110x 10xxxxxx 10xxxxxx
10xxxxxx 10xxxxxx 10xxxxxx |
xxx 的位置由字符编码数的二进制表示的位填入,越靠右的
x 具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。
例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx
10xxxxxx 10xxxxxx。将6C49写成二进制是:0110
110001 001001,
用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1
89。
UTF-8有以下一些特点:
2)UTF-16:以16bit为单元对UCS/Unicode进行编码,对于小于0x10000的UCS码,UTF-16编码就等于UCS码对应的16位无符号整数。对于不小于
0x10000的UCS码,定义了一个算法。不过由于实际使用的UCS2,或者UCS4的BMP必然小于0x10000,所以就目前而言,可以认为UTF-16和UCS-2基本相同。另外,UTF-16是变长码(2个或4个byte),且以双字节作为编码单位所以实际存储、传输时与CPU字序有关,因此java有区分UTF-16BE和UTF-16LE两种charset。
3)UTF-32:仅使用了Unicode范围(0到0x10FFFF)的32位编码,相当于UCS-4的子集。
字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用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个汉字。
GB18030 的编码采用单字节、双字节和4字节方案。其中单字节、双字节和GBK是完全兼容的。4字节编码的码位就是收录了CJK扩展A的6582个汉字。例如:UCS的0x3400在GB18030中的编码应该是8139EF30,UCS的0x3401在GB18030中的编码应该是8139EF31。
GB 18030是中国所有非手持/嵌入式计算机系统的强制实施标准。
ISO与Unicode协会是两个不同的组织,都试图设计统一字符集,ISO开发了ISO
10646项目,Unicode协会开发了Unicode项目。在1991年前后,双方都认识到世界不需要两个不兼容的字符集。于是它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从Unicode2.0开始,Unicode项目采用了与ISO
10646-1相同的字库和字码。目前两个项目仍都存在,并独立地公布各自的标准。Unicode协会现在的最新版本是2005年的Unicode
4.1.0。ISO的最新标准是ISO
10646-3:2003。
总结起来,Unicode与ISO
10646是两个不同的标准,但定义的内容基本相同(BMP部分完全相同),所以基本可以理解为UCS
= Unicode。
ASCII、GB2312、GBK、UCS、Unicode这些都是字符集。字符集定义了字符编码表,即规定了整数到字符的对应关系,但并没有规定与这些整数如何表示、保存、传输,这些是由编码格式规定的。例如“汉”字的UCS/Unicode编码是6C49,你可以用4个ascii数字来传输、保存这个编码;也可以用
utf-8编码:3个连续的字节E6
B1 89来表示它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的方案。UTF-8的一个特别的好处是它与ISO-8859-1完全兼容。UTF是“UCS/Unicode
Transformation Format”的缩写。
big endian 和little
endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big
endian。如果将49写在前面,就是little endian。
我们一般将endian翻译成“字节序”,将big
endian和little endian称作“大尾”和“小尾”。
Unicode与UTF的关系不用多说,一个是字符集,一个是编码方式。
UTF-16我理解更适合程序在内存中表示字符、操作字符,因为每个字符都是2个字节的,便于cpu处理,效率高。
UTF-8更适合作为存储和网络传输的编码方式,是由其特点决定的,一是容错性好;二是每个字符的字节数可以由第一个字节开头的“1”的个数确定,方便判断字符的结束;三是UTF-8是以字节为单位编码的,不存在与cpu相关的字节序的问题;四是UTF-8处理ASCII字符有优势,只用1个字节,省空间,当然这个优势在处理中文等多字节字符时是没有的。
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来标记文本文件的编码方式的。