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>,其中对应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,对应U+0430,对应U+4E8C,对应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代码单元,字节序列可以被用作UTF-8的BOM,它与字节序无关,只是表明UTF-8编码;
  • UTF-16BE编码方案:以大尾端形式序列化UTF-16代码单元,字节序列被用作UTF-16BE的BOM;
  • UTF-16LE编码方案:以小尾端形式序列化UTF-16代码单元,字节序列被用作UTF-16LE的BOM;
  • UTF-16编码方案:以大尾端或小尾端形式序列化UTF-16代码单元,检查初始字节序列,表明是大尾端,而表明是小尾端,否则若均不是则默认是大尾端;
  • UTF-32BE编码方案:以大尾端形式序列化UTF-32代码单元,字节序列<00 00 FE FF>被用作UTF-32BE的BOM;
  • UTF-32LE编码方案:以小尾端形式序列化UTF-32代码单元,字节序列被用作UTF-32LE的BOM;
  • UTF-32编码方案:以大尾端或小尾端形式序列化UTF-32代码单元,检查初始字节序列,<00 00 FE FF>表明是大尾端,而表明是小尾端,否则若均不是则默认是大尾端。

以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
精通正则表达式

你可能感兴趣的:(Unicode字符集与编码)