Unicode是一种字符编码方法,不过它是由国际组织设计,可以容纳全世界所有语言文字的编码方案。Unicode的学名是"Universal Multiple-Octet Coded Character Set",简称为UCS。UCS可以看作是"UnicodeCharacter Set"的缩写。
UCS有两种格式:UCS-2和UCS-4。顾名思义,UCS-2就是用两个字节编码,UCS-4就是用4个字节(实际上只用了31位,最高位必须为0)编码。 UCS-2有2^16=65536个码位,当时是没有问的,经过一段时间,不可避免的事情发生了,unicode字符超过了65536个,主要原因是加入了大量汉语、日语、韩语等表意文字,这里加入的是不常用的汉字,常用的汉字早已加入进去。
字符集 |
字数 |
Unicode 编码 |
20902字 |
4E00-9FA5 |
|
74字 |
9FA6-9FEF |
|
6582字 |
3400-4DB5 |
|
42711字 |
20000-2A6D6 |
|
4149字 |
2A700-2B734 |
|
222字 |
2B740-2B81D |
|
5762字 |
2B820-2CEA1 |
|
7473字 |
2CEB0-2EBE0 |
|
214字 |
2F00-2FD5 |
|
115字 |
2E80-2EF3 |
|
477字 |
F900-FAD9 |
|
542字 |
2F800-2FA1D |
|
81字 |
E815-E86F |
|
452字 |
E400-E5E8 |
|
207字 |
E600-E6CF |
|
36字 |
31C0-31E3 |
|
12字 |
2FF0-2FFB |
|
43字 |
3105-312F |
|
22字 |
31A0-31BA |
|
〇 |
1字 |
3007 |
这张表中蓝色的为最后补加的不常用字符。我们在程序中经常要判断是否有中文,字符编码如果落在[\u4e00-\u9fa5]区间就认为有中文,其他情况就不管了。
UCS-4 理论上有2^31=2147483648个码位,但是实际上没有那么多,因为要兼容ucs-2,还要尽量节省空间。
UCS-4 实际编码空间从U+0000到+10FFFF,共有1,112,064个码位(codepoint)可用来映射字符,这个数量满足需求。Unicode的编码空间可以划分为17个平面(plane),每个平面包含65,536个码位。17个平面的码位可表示为从U+xx0000到U+xxFFFF,其中xx表示从0x00到0x10,共计17个平面。第一个平面称为基本多语言平面(Basic Multilingual Plane, BMP),或称第零平面(Plane 0),或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。其他平面称为辅助平面(Supplementary Planes),辅助平面有16个,共包含16*65536=2^20个码位。基本多语言平面内还有一块保留区,从U+D800到U+DFFF,将这个区分成两块U+D800~U+DBFF用于第一个代码单元,U+DC00~U+DFFF用于第二个代码单元,这两个代码单元可表于的码位2^10*2^10=2^20,正好满足要求。
UCS只是规定如何编码,并没有规定如何传输、保存这些编码。
规定存储方式的称为UTF(Unicode Transformation Format),其中就有UTF-8、UTF-16和UTF-32。
UTF-16
UTF-16由RFC2781规定,它使用两个字节来表示一个代码点。
不难猜到,UTF-16是完全对应于UCS-2的,即把UCS-2规定的代码点通过Big Endian(大端)或LittleEndian(小端)方式直接保存下来。UTF-16包括三种:UTF-16,UTF-16BE(Big Endian),UTF-16LE(LittleEndian)。
其实BOM是个小聪明的想法。只要出现 FEFF 或 FFFE就可以判断出是Big Endian还是Little Endian。如果没有BOM编译器会智能的分析,从而找出是Big Endian还是Little Endian。
举个例子。“ABC”这三个字符用各种方式编码后的结果如下:
UTF-16 |
00 41 00 42 00 43 |
UTF-16 BE |
00 41 00 42 00 43 |
UTF-16 LE |
41 00 42 00 43 00 |
UTF-16 BE BOM |
FE FF 00 41 00 42 00 43 |
UTF-16 LE BOM |
FF FE 41 00 42 00 43 00 |
大端和小端的区别就是一个字的两个字节对掉。
另外,UTF-16还能表示一部分的UCS-4代码点——U+10000~U+10FFFF。表示算法比较复杂,简单说明如下:
- 从代码点U中减去0x10000,得到U'。这样U+10000~U+10FFFF就变成了 0x00000~0xFFFFF。
- 用20位二进制数表示U'。 U'=yyyyyyyyyyxxxxxxxxxx
- 将前10位和后10位用W1和W2表示,W1=110110yyyyyyyyyy,W2=110111xxxxxxxxxx,则 W1 = D800~DBFF,W2 = DC00~DFFF。
例如,U+12345表示为 D8 08 DF 45(UTF-16BE),或者08 D8 45 DF(UTF-16LE)。
但是由于这种算法的存在,造成UCS-2中的 U+D800~U+DFFF 变成了无定义的字符。
UTF-32
UTF-32用四个字节表示代码点,这样就可以完全表示UCS-4的所有代码点,而无需像UTF-16那样使用复杂的算法。与UTF-16类似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三种编码,UTF-32也同样需要BOM字符。仅用'ABC'举例:
UTF-32BE |
00 00 00 41 00 00 00 42 00 00 00 43 |
UTF-32LE |
41 00 00 00 42 00 00 00 43 00 00 00 |
UTF-32(Big Endian) |
00 00 FE FF 00 00 00 41 00 00 00 42 00 00 00 43 |
UTF-32(Little Endian) |
FF FE 00 00 41 00 00 00 42 00 00 00 43 00 00 00 |
UTF-32(不带BOM) |
00 00 00 41 00 00 00 42 00 00 00 43 |
UTF-8
UTF-16和UTF-32的一个缺点就是它们固定使用两个或四个字节,这样在表示纯ASCII文件时会有很多00字节,造成浪费。而RFC3629定义的UTF-8则解决了这个问题。
UTF-8来说,每一个字节的前两位尤为重要,按照前两位的不同,一共有四种排列组合:00xxxxxx,01xxxxxx,10xxxxxx,11xxxxxx。
(1)所有以0开始的字节,都与原来的ASCII码兼容,也就是说,0xxxxxxx不需要额外转换,就是我们平时用的ASCII码。
(2)所有以10开始的字节,都不是每个UNICODE的第一个字节,都是紧跟着前一位。例如:10110101,这个字节不可以单独解析,必须通过前一个字节来解析,如果前一个也是10开头,就继续前嗍。
(3)所有以11开始的字节,都表示是UNICODE的第一个字节,而且后面紧跟着若干个以10开头的字节。如果是110xxxxx(就是最左边的0的左边有2个1),代表后面还有1个10xxxxxx;如果是1110xxxx(就是最左边的0的左边有3个1),代表后面还有2个10xxxxxx;以此类推,一直到1111110x。
具体的表格如下:
1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx10xxxxxx
UCS-2 (UCS-4) |
位序列 |
第一字节 |
第二字节 |
第三字节 |
第四字节 |
U+0000 ~ U+007F |
00000000-0xxxxxxx |
0xxxxxxx |
|||
U+0080 ~ U+07FF |
00000xxx-xxyyyyyy |
110xxxxx |
10yyyyyy |
||
U+0800 ~ U+FFFF |
xxxxyyyy-yyzzzzzz |
1110xxxx |
10yyyyyy |
10zzzzzz |
|
U+10000~U+1FFFFF |
00000000-000wwwxx-xxxxyyyy-yyzzzzzzz |
11110www |
10xxxxxx |
10yyyyyy |
10zzzzzz |
可见,ASCII字符(U+0000~U+007F)部分完全使用一个字节,避免了存储空间的浪费。另外,从上表中可以看出,单字节编码的第一字节为[00-7F],双字节编码的第一字节为[C2-DF],三字节编码的第一字节为[E0-EF]。这样只要看到第一个字节的范围就可以知道编码的字节数。这样也可以大大简化算法。