Unicode字符集与编码
本文简要介绍Unicode的相关知识,以澄清部分概念。
Unicode字符集
Unicode是一个字符集,它是所有其他广泛使用字符集的超集,包含了来自ISO/IEC 6937、ISO/IEC 8859家族、ANSI Z39.64、KS X 1001、JIS X 0208、JIS X 0212、JIS X 0213、GB 2312、GB 18030、HKSCS和CNS 11643等字符集的字符。
Unicode标准完全遵守国际标准ISO/IEC 10646:2017, Information Technology—Universal Coded Character Set (UCS)。Unicode 1.0版于1991年发布,2.0版于1996年发布,写作本文时Unicode的最新版本是12.0,其他各版本的发布历史可参考History of Unicode Release。
术语定义
Unicode中的常用术语如下:
- 代码点(code point):Unicode中的每个数值都对应一个字符,这个数值就叫做代码点,通常使用十六进制表示,以“U+”开头,如U+0041表示英文字符A;
- 补充代码点:位于U+10000..U+10FFFF之间的代码点,对应的字符叫做补充字符。Unicode 2.0引入了补充字符,但直到3.1才为补充字符赋值;
- 代码空间(code space):代码点的合法范围,0至10FFFF16,即有1,114,112个代码点;
- 代码单元(code unit):能表示一个单元的编码文本的最小位的组合;
- 平面(plane):一个平面是65,536(1000016)个连续的Unicode代码点,第一个代码点是65536的整数倍。平面的编号从0至16,所以平面0包括U+0000..U+FFFF,平面1包括U+10000..U+1FFFF,…,平面16包括U+100000..U+10FFFF。平面0叫做基本多语言平面(BMP),其余平面叫做补充平面;
- 属性(property):字符的性质,如大写还是小写,是否是数字;
- 字母表(script):书写系统中字母和其他书写符号的集合,如俄语是西里尔字母表的子集;
- 区块(block):Unicode中的一组字符,如Tibetan区块包含U+0F00..U+0FFF共256个代码点。
组合字符
Unicode中的每个代码点都对应一个字符,但是一个字符可以对应多个代码点,代码点与字符并不是一一映射。
以字符Á为例,它有两种表示方法:
- 单个代码点U+00C1;
- 两个代码点U+0041(A)与U+0301( ́)一起表示。
而对字符Ệ,则有三种表示方法:
- 单个代码点U+1EC6;
- 三个代码点U+0045(E)、U+0323( ̣)和U+0302( ̂)一起表示;
- 三个代码点U+0045(E)、U+0302( ̂)和U+0323( ̣)一起表示,注意与第二种顺序不同。
Unicode提供了许多组合字符(Combining Character或Combining Mark)修饰基本字符,正如Ệ所示,多个组合字符的顺序也可以不同。为什么会出现这样的情况呢?这是为了保证Unicode与其他字符集之间转换的简易型,如ISO 8859-1/2/3/4/9/10/14/15/16中Á的编码是C1,Unicode就使用单个代码点U+00C1表示该字符。
归一化
组合字符为字符比较带来了问题,因为看着一样的字符实际可能是由不同代码点表示的,Unicode Standard Annex #15定义了两种相等:
- 规范相等(canonical equivalence):规范相等是一种字符或者序列间的基本相等形式,它们表示相同的抽象字符,当正确显示时总是应该有相同的外观或行为,如Ç与C和◌̧的组合;
- 兼容相等(compatibility equivalence):兼容相等则是一种较弱的相等形式,字符或者序列表示相同的抽象字符,但可以有不同的外观或行为。如ℌ与H。
Normalization FAQ指出程序总是应该使用规范相等执行比较相等的操作,最简单的方法就是归一化字符串:如果字符串被转换成了归一化形式,则规范相等的串会有完全相同的二进制表示。为了使用规范相等,可以使用Unicode标准提供的NFC与NFD归一化形式。Unicode标准一共定义了四种归一化(normalization)形式:
- Normalization Form D (NFD):规范分解;
- Normalization Form C (NFC):规范分解,接着规范组合,一般使用NFC;
- Normalization Form KD (NFKD):兼容分解;
- Normalization Form KC (NFKC):兼容分解,接着兼容组合。
组合字符对正则表达式的影响:如点号是匹配单个代码点,还是由基本字符和组合字符组成的整个代码点序列?在实践中,许多程序的点号匹配单个代码点,无论它代表基本字符还是组合字符,即Ệ(U+0045、U+0302和U+0323)由三个点号而不是一个点匹配。
Unicode编码形式
Unicode标准支持三种编码形式(encoding form),分别是UTF-32、UTF-16和UTF-8。UTF(Unicode Transformation Format)编码是一种从Unicode代码点到唯一字节序列的映射,Unicode标准第3.9节是UTF的正式定义。
UTF-32
UTF-32编码形式使用32位无符号代码单元表示Unicode代码点:
- 位于0000D80016..0000DFFF16间的的代码单元是非法的;
- 大于0010FFFF16的代码单元是非法的;
- 以代码点序列<004D, 0430, 4E8C, 10302>为例,它会被编码成<0000004D 00000430 00004E8C 00010302>。
UTF-16
UTF-16编码形式使用单个16位无符号代码单元表示位于U+0000..U+D7FF和U+E000..U+FFFF之间的代码点,使用替代对表示U+10000..U+10FFFF之间的代码点:
- 为了使UTF-16能表示U+10000以上的代码点,Unicode引入了替代(surrogate)代码点,高位替代代码点位于U+D800..U+DBFF,低位替代代码点位于U+DC00..U+DFFF,一个替代对由一个高位替代代码点和一个低位替代代码点组成;
- 独立的位于D80016..DFFF16间的的代码单元是非法的;
- 以代码点序列<004D, 0430, 4E8C, 10302>为例,它会被编码成<004D 0430 4E8C D800 DF02>,其中<D800 DF02>对应U+10302。
代码点 | 代码点二进制表示 | UTF-16 |
---|---|---|
U+0000..U+D7FF和U+E000..U+FFFF | xxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxx |
U+10000..U+10FFFF | 000uuuuuxxxxxxxxxxxxxxxx | 110110wwwwxxxxxx 110111xxxxxxxxxx |
其中wwww = uuuuu - 1
UTF-8:
UTF-8编码形式使用1到4个无符号字节序列表示Unicode代码点:
- 以代码点序列<004D, 0430, 4E8C, 10302>为例,它会被编码成<4D D0 B0 E4 BA 8C F0 90 8C 82>,其中<4D>对应U+004D,<D0 B0>对应U+0430,<E4 BA 8C>对应U+4E8C,<F0 90 8C 82>对应U+10302。
代码点 | 第1个字节 | 第2个字节 | 第3个字节 | 第4个字节 |
---|---|---|---|---|
00000000 0xxxxxxx | 0xxxxxxx | |||
00000yyy yyxxxxxx | 110yyyyy | 10xxxxxx | ||
zzzzyyyy yyxxxxxx | 1110zzzz | 10yyyyyy | 10xxxxxx | |
000uuuuu zzzzyyyy yyxxxxxx | 11110uuu | 10uuzzzz | 10yyyyyy | 10xxxxxx |
Unicode编码方案
Unicode编码方案(encoding scheme)是指Unicode编码形式的一种特定字节序列化,包括对字节顺序标记(BOM)的处理。Unicode标准支持七种编码方案:
- UTF-8编码方案:以与代码单元完全相同的顺序序列化UTF-8代码单元,字节序列<EF BB BF>可以被用作UTF-8的BOM,它与字节序无关,只是表明UTF-8编码;
- UTF-16BE编码方案:以大尾端形式序列化UTF-16代码单元,字节序列<FE FF>被用作UTF-16BE的BOM;
- UTF-16LE编码方案:以小尾端形式序列化UTF-16代码单元,字节序列<FF FE>被用作UTF-16LE的BOM;
- UTF-16编码方案:以大尾端或小尾端形式序列化UTF-16代码单元,检查初始字节序列,<FE FF>表明是大尾端,而<FF FE>表明是小尾端,否则若均不是则默认是大尾端;
- UTF-32BE编码方案:以大尾端形式序列化UTF-32代码单元,字节序列<00 00 FE FF>被用作UTF-32BE的BOM;
- UTF-32LE编码方案:以小尾端形式序列化UTF-32代码单元,字节序列<FF FE 00 00>被用作UTF-32LE的BOM;
- UTF-32编码方案:以大尾端或小尾端形式序列化UTF-32代码单元,检查初始字节序列,<00 00 FE FF>表明是大尾端,而<FF FE 00 00>表明是小尾端,否则若均不是则默认是大尾端。
以U+004D为例,三种编码形式的代码单元如下:
UTF-8代码单元 | UTF-16代码单元 | UTF-32代码单元 |
---|---|---|
4D | 004D | 0000004D |
五种编码方案如下:
UTF-8 | UTF-16BE | UTF-16LE | UTF-32BE | UTF-32LE |
---|---|---|---|---|
4D | 00 4D | 4D 00 | 00 00 00 4D | 4D 00 00 00 |
实现
不同的编程语言对Unicode的支持程度不同。
Java
Java语言规范指出Java使用UTF-16编码表示文本序列。各JDK版本与遵守的Unicode版本对应关系如下:
JDK版本 | 遵守的Unicode版本 |
---|---|
1.1之前 | 1.1.5 |
1.1 | 2.0 |
1.1.7 | 2.1 |
1.4 | 3.0 |
5.0 | 4.0 |
7 | 6.0 |
8 | 6.2 |
9 | 8.0 |
11 | 10.0 |
12 | 11.0 |
java.text包的Normalizer类的normalize方法提供了归一化功能,支持全部四种归一化形式。
实例
UTF-8
U+0430
00000yyy yyxxxxxx
00000100 00110000
11010000
10110000
U+4E8C
zzzzyyyy yyxxxxxx
01001110 10001100
11100100
10111010
10001100
U+10302
000uuuuu zzzzyyyy yyxxxxxx
00000001 00000011 00000010
11110000
10010000
10001100
10000010
参考文献
https://www.unicode.org
http://www.oracle.com/us/technologies/java/supplementary-142654.html
http://reedbeta.com/blog/programmers-intro-to-unicode/
https://www.infoq.com/presentations/unicode-history
精通正则表达式