Unicode 编码规则
ANSI,American National Standards Institute 美国国家标准学会,由这个标准学会制订的一种编码规则,也叫 MBCS(Muilti-Bytes Charecter Set,多字节字符集)。
在最初的时候,Internet 上只有一种字符集—— ANSI 的 ASCII 字符集,它使用 7 bits 来表示一个 字符,总共表示 128 个字符,其中包括了 英文字母、数字、标点符号等常用字符。 之后,又进行扩展,使用 8 bits 表示一个字符,可以表示 256 个字符,主要在原来的 7 bits 字符集的基础上加入了一些特殊符号。 后来,由于各国语言的加入,ASCII 已经不能满足信息交流的需要,为了能够表示其它国家的文字,各国在 ASCII 的基础上制定了自己的字符集,这些从 ANSI 标准派生的字符集被习惯的统称为 ANSI 字符集,它们正式的名称应该是 MBCS(Multi-Byte Chactacter System,即多字节字符系统)。 这些派生字符集的特点是以 ASCII 127 bits 为基础,兼容 ASCII 127,他们使用大于 128 的编码作为一个 Leading Byte,紧跟在 Leading Byte 后的第二(甚至第三)个字符与 Leading Byte 一起作为实际的编码。 这样的字符集有很多,我们常见的 GB-2312 就是其中之一。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。 一个很大的缺点是:同一个编码值,在不同的编码体系里代表着不同的字,这样就容易造成混乱,导致了 Unicode 码 的诞生。 为了统一所有文字的编码,Unicode 应运而生。Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题了。
字符
分配一个唯一的数字 ID(学名为码位/码点/Code Point)。
码位
转换为 字节序列
的规则。
Unicode 编码点分为 17 个平面(plane),每个平面包含 2^16(即65536)个码位(Code Point)。
17 个平面的码位可表示为从 U+xx0000 到 U+xxFFFF,其中 xx 表示十六进制值从 00 到 10 ,共计 17 个平面。
17 * 65536 = 1114112,所以 Unicode 能表示 1114112 个不同的字符,每个字符都有对应的唯一数字。
编码规则中还有另外一个重要的概念,编码单元(Code Unit),指编码一个字符需要的最少字节数, 比如 UTF-8 需要最少一个字节,UTF-16 最少两个字节,UCS-2 两个字节,UCS-4 和 UTF-32 四个字节。 UTF-8 和 UTF-16 是变长编码,UCS-2、UCS-4 和 UTF-32 是定长编码。
前面提到过:Unicode 编码点分为 17 个平面(plane),每个平面包含 2^16(即 65536)个码位(code point),而第一个平面称为“基本多语言平面”(Basic Multilingual Plane,简称 BMP),其余平面称为“辅助平面”(Supplementary Planes)。其中“基本多语言平面”(0~0xFFFF)中 0xD800~0xDFFF 之间的码位作为保留,未使用。UCS-2 只能编码“基本多语言平面”中的字符,此时 UTF-16 与 UCS-2 的编码一样(都直接使用 Unicode 的码位作为编码值),例:“汉”在 Unicode 中的码位为 6C49,而在 UTF-16 编码也为 6C49。另外,UTF-16 还可以利用保留下来的 0xD800-0xDFFF 区段的码位来对“辅助平面”的字符的码位进行编码,因此 UTF-16 可以为 Unicode 中所有的字符编码。
UTF-16 中如何对“辅助平面”进行编码呢?
Unicode 的码位区间为 0~0x10FFFF,除“基本多语言平面”外,还剩 0xFFFFF 个码位(并且其值都大于或等于 0x10000)。对于“辅助平面”内的字符来说,如果用它们在 Unicode 中码位值减去 0x10000,则可以得到一个 0~0xFFFFF 的区间(该区间中的任意值都可以用一个 20-bits 的数字表示)。该数字的前 10 位(bits)加上 0xD800,就得到 UTF-16 四字节编码中的前两个字节;该数字的后 10 位(bits)加上 0xDC00,就得到 UTF-16 四字节编码中的后两个字节。例如:
“𪺫” 这个汉字的 Unicode 码位值为 2AEAB,减去 0x10000 得到 1AEAB(二进制值为 0001 1010 1110 1010 1011),前 10 位加上 D800 得到 D86B,后 10 位加上 DC00 得到 DEAB。于是该字的 UTF-16 编码值为 D86BDEAB(该值为大端表示,小端为 6BD8ABDE)
string hex = "6BD8ABDE"; byte[] hexByteArr = Enumerable.Range(0, hex.Length) .Where(x => x % 2 == 0) .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) .ToArray(); string word = UnicodeEncoding.Unicode.GetString(hexByteArr);
UTF-8 的编码规则:
(1) 对于 ASCII 码中的符号,使用单字节编码,其编码值与 ASCII 值相同。其中 ASCII 值的范围为 0~0x7F,所有编码的二进制值中第一位为 0(这个正好可以用来区分单字节编码和多字节编码)。
(2) 其它字符用多个字节来编码(假设用 N 个字节),多字节编码需满足:第一个字节的前 N 位都为 1,第 N+1 位为 0,后面 N-1 个字节的前两位都为 10,这 N 个字节中其余位全部用来存储 Unicode 中的码位值。
字节数 | Unicode | 十进制 | UTF-8 编码 |
---|---|---|---|
1 | 000000-00007F | 0-127 | 0xxxxxxx |
2 | 000080-0007FF | 128-2047 | 110xxxxx 10xxxxxx |
3 | 000800-00FFFF | 2048-65535 | 1110xxxx 10xxxxxx 10xxxxxx |
4 | 010000-10FFFF | 65536-1114111 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
UTF 是英文 Unicode Transformation Format 的缩写,可以翻译成 Unicode 字符集转换格式,即怎样将 Unicode 定义的数字转换成程序数据 。
UTF 系列编码方案(UTF-8、UTF-16、UTF-32)均是由 Unicode 编码方案衍变而来,以适应不同的数据存储或传递,它们都可以完全表示 Unicode 标准中的所有字符。
目前,这些衍变方案中 UTF-8 和 UTF-16 被广泛使用,而 UTF-32 则很少被使用。
对比 | UTF-8 | UTF-16 | UTF-32 | UCS-2 | UCS-4 |
---|---|---|---|---|---|
编码空间 | 0-10FFFF | 0-10FFFF | 0-10FFFF | 0-FFFF | 0-7FFFFFFF |
最少编码字节数 | 1 | 2 | 4 | 2 | 4 |
最多编码字节数 | 4 | 4 | 4 | 2 | 4 |
是否依赖字节序 | 否 | 是 | 是 | 是 | 是 |
BOM | Encoding | Endian |
---|---|---|
2B 2F 76 38 2D | UTF-7 | endianness |
EF BB BF | UTF-8 | endianness |
FF FE | UTF-16-LE | little endian |
FE FF | UTF-16-BE | big endian |
FF FE 00 00 | UTF-32-LE | little endian |
00 00 FE FF | UTF-32-BE | big endian |
字符"ZERO WIDTH NO-BREAK SPACE"的 UTF-8 编码是 EF BB BF。
更多字节序的问题,请参考 维基百科 字节序
Unicode 误区
- Unicode 只不过是 16 比特编码。一些人误认为 Unicode 只不过是 16 比特编码,每个字符占用 16 比特,因此一共有 65,536 个可能的字符。实际上这是不正确的。这样是关于 Unicode 的最大误解,所以也难怪一些人会这么想。
- 任何未分配的代码点都可以用于内部用途?错。最终,那些未分配的地方都会被某个字符使用。你应该使用私有用途代码点,或非字符代码点。
- 每个 Unicode 代码点都表示一个字符?错。有许多非字符代码点(FFFE,FFFF,1FFFE,……),还有许多代理代码点、私有代码点和未分配的代码点,还有控制和格式字符(RLM,ZWNJ,……)
-
字符映射是一对一的?错。映射关系也可能是:
- 一对多:(ß → SS )
- 上下文相关:(…Σ ↔ …ς 和 …ΣΤ… ↔ …στ… )
- 语言相关:( I ↔ ı 和 İ ↔ i )