人类交换应用 ABC 等字符,但计算机只意识 01。因而,就须要将人类的字符,转换成计算机意识的二进制编码。这个过程就是字符编码。

ASCII

最简略、罕用的字符编码就是 ASCII(American Standard Code for Information Interchange,美国信息替换规范代码),它将美国人最罕用的 26 个英文字符的大小写和罕用的标点符号,编码成 0127 的数字。例如 A 映射成 65 (0x41),这样计算机中就能够用 0100 0001 这组二进制数据,来示意字母 A 了。

ASCII 编码的字符能够分成两类:

  • 控制字符:0 - 31127 (0x00 - 0x1F0x7F)
  • 可显示字符:32 - 126 (0x20 - 0x7E)

具体字符表能够参考:ASCII - 维基百科,自在的百科全书。

Unicode

ASCII 只编码了美国罕用的 128 个字符。显然不足以满足世界上这么多国家、这么多语言的字符应用。于是各个国家和地区,就都开始对本人须要的字符设计其余编码方案。例如,中国有本人的 GB2312,不够用了之后又扩大了 GBK,还是不够用,又有了 GB18030。欧洲有一系列的 ISO-8859 编码。这样各国人民就都能够在计算机上解决本人的语言文字了。

但每种编码方案,都只思考了本人用到的字符,没方法跨服交换。如果一篇文档里,同时应用了多种语言的字符,总不能别离指定哪个字符应用了那种编码方式。

如果能对立给世界上的所有字符调配编码,就能够解决跨服交换的问题了,Unicode 就是来干这个事件的。

Unicode 对立编码了世界上大部分的字符,例如将 A 编码成 0x00A1,将 编码成 0x4E2D,将 编码成 0x03B1。这样,中国人、美国人、欧洲人,就能够应用同一种编码方式交换了。

一个 Unicode 字符能够应用 U+ 和 4 到 6 个十六进制数字来示意。例如 U+0041 示意字符 AU+4E2D 示意字符 U+03B1 示意字符

Unicode 最后编码的范畴是 0x00000xFFFF,也就是两个字节,最多 65536 (2^16) 个字符。但随着编码的字符越来越多,两个字节的编码空间曾经不够用,因而又引入了 16 个辅助立体,每个辅助立体同样最多蕴含 65536 个字符。原来的编码范畴称为根本立体,也叫第 0 立体。

各立体的字符范畴和名称如下表:

立体字符范畴名称
0 号立体U+0000 - U+FFFF根本多文种立体 (Basic Multilingual Plane, BMP)
1 号立体U+10000 - U+1FFFF多文种补充立体 (Supplementary Multilingual Plane, SMP)
2 号立体U+20000 - U+2FFFF表意文字补充立体 (Supplementary Ideographic Plane, SIP)
3 号立体U+30000 - U+3FFFF表意文字第三立体 (Tertiary Ideographic Plane, TIP)
14 号立体U+E0000 - U+EFFFF特地用处补充立体
15 号立体U+F0000 - U+FFFFF保留作为私人应用区(A 区)(Private Use Area-A, PUA-A)
16 号立体U+100000 - U+10FFFF保留作为私人应用区(B 区)(Private Use Area-B, PUA-B)

每个立体内还会进一步划分成不同的区段。每个立体和区段具体阐明参考 Unicode字符立体映射 - 维基百科,自在的百科全书;汉字相干的区段阐明参考 中日韩对立表意文字 - 维基百科,自在的百科全书。Unicode 所有字符按立体和区段查找,能够参考 Roadmaps to Unicode;按区域和语言查找能够参考 Unicode Character Code Charts。

字符编码的基本概念

“字符编码”是一个含糊、抽象的概念,为了进一步阐明字符编码的过程,须要将其拆解为一些更加明确的概念:

字符 (Character)

人类应用的字符。例如:

  • A
  • 等。

编码字符集 (Coded Character Set, CCS)

把一些字符的汇合 (Character Set) 中的每个字符 (Character),映射成一个编号或坐标。例如:

  • 在 ASCII 中,把 A 编号为 65 (0x41);
  • 在 Unicode 中,把 编号为 0x4E2D
  • 在 GB2312 中,把 映射到第 54 区第 0 位。

这个映射的编号或坐标,叫做 Code Point。

Unicode 就是一个 CCS。

字符编码表 (Character Encoding Form, CEF)

把 Code Point 转换成特定长度的整型值的序列。这个特定长度的整型值叫做 Code Unit。例如:

  • 在 ASCII 中,0x41 这个 Code Point 会被转换成 0x41 这个 Code Unit;
  • 在 UTF-8 中,0x4E2D 这个 Code Point 会被转换成 0xE4 B8 AD 这三个 Code Unit 的序列。

咱们罕用的 UTF-8、UTF-16 等,就是 CEF。

字符编码方案 (Character Encoding Scheme, CES)

把 Code Unit 序列转换成字节序列(也就是最终编码后的二进制数据,供计算机应用)。例如 :

  • 0x0041 这个 Code Unit,应用大端序会转换成 0x00 41 两个字节;
  • 应用小端序会转换成 0x41 00 两个字节。

UTF-16 BE、UTF-32 LE 等,就是 CES。


这些概念间的关系如下:

@startumlhide empty descriptionstate Characterstate CodePointstate CodeUnitsstate BytesCharacter : ACodePoint : 0x41CodeUnits : 0x0041Bytes : 0x41 0x00Character -right-> CodePoint : CCS (Unicode)CodePoint -right-> CodeUnits : CEF (UTF-16)CodeUnits -right-> Bytes : CES (UTF-16 LE)@enduml

因而,咱们说 ASCII 是“字符编码”时,“字符编码”指的是下面从 Character 到字节数组的整个过程。因为 ASCII 足够简略,两头的 Code Point 到 Code Unit,再到字节数组,都是一样的,没必要拆开说。

而咱们说 Unicode 是“字符编码”时,“字符编码”其实指的仅是下面的 CCS 局部。

同理,ASCII、Unicode、UTF-8、UTF-16、UTF-16 LE,都能够抽象的叫做“字符编码”,但每个“字符编码”示意的含意都是不同的。可能是 CCS、CEF、CES,也可能是整个过程。

Unicode 转换格局

Unicode 只是把字符映射成了 Code Point (字符编码表,CCS)。将 Code Point 转换成 Code Unit 序列(字符编码表,CEF),再最终将 Code Unit 序列转换成字节序列(字符编码方案,CES),有多种不同的实现形式。这些实现形式叫做 Unicode 转换格局 (Unicode Transformation Format, UTF)。次要包含:

  • UTF-32
  • UTF-16
  • UTF-8

UTF-32

UTF-32 将每个 Unicode Code Point 转换成 1 个 32 位长的 Code Unit。

UTF-32 是固定长度的编码方案,每个 Code Unit 的值就是其 Code Point 的值。例如 0x00 00 00 41 这个 Code Unit,就示意了 0x0041 这个 Code Point。

UTF-32 的一个 Code Unit,须要转换成 4 个字节的序列。因而,有大端序 (UTF-32 BE) 和小端序 (UTF-32 LE) 两种转换形式。

例如 0x00 00 00 41 这个 Code Unit,应用 UTF-32 BE 最终会编码为 0x00 00 00 41;应用 UTF-32 LE 最终会编码为 0x41 00 00 00

UTF-16

UTF-16 将每个 Unicode Code Point 转换成 1 到 2 个 16 位长的 Code Unit。

对于根本立体的 Code Point(0x00000xFFFF),每个 Code Point 转换成 1 个 Code Unit,Code Unit 的值就是其对应 Code Point 的值。例如 0x0041 这个 Code Unit,就示意了 0x0041 这个 Code Point。

对于辅助立体的 Code Point(0x0100000x10FFFF),每个 Code Point 转换成 2 个 Code Unit 的序列。如果还是间接应用 Code Point 数值转换成 Code Unit,就有可能和根本立体的编码重叠。例如 U+010041 如果转换成 0x00010x0041 这两个 Code Unit,解码的时候没方法晓得这是 U+010041 一个字符,还是 U+0001U+0041 两个字符。

为了让辅助立体编码的两个 Code Unit,都不与根本立体编码的 Code Unit 重叠,就须要利用根本立体中一个非凡的区段了。根本立体中规定了从 0xD8000xDFFF 之间的区段,是永恒保留不映射任何字符的。UTF-16 将辅助立体的 Code Point,编码成一对在这个范畴内的 Code Unit,叫做代理对。这样解码的时候,如果解析到某个 Code Unit 在 0xD8000xDFFF 范畴内,就晓得他不是根本立体的 Code Unit,而是要两个 Code Unit 组合在一起去示意 Code Point。

具体转换形式是:

  1. 将辅助立体的 Code Point 的值 (0x010000 - 0x10FFFF),减去 0x010000,失去 0x000000xFFFFF 范畴内的一个数值,也就是最多 20 个比特位的数值
  2. 将前 10 位的值(范畴在 0x00000x03FF),加上 0xD800,失去范畴在 0xD8000xDBFF 的一个值,作为第一个 Code Unit,称作高位代理或前导代理
  3. 将后 10 位的值(范畴在 0x00000x03FF),加上 0xDC00,失去范畴在 0xDC000xDFFF 的一个只,作为第二个 Code Unit,称作低位代理或后尾代理

根本立体中的 0xD800 - 0xDBFF0xDC00 - 0xDFFF 这两个区段,也别离叫做 UTF-16 高半区 (High-half zone of UTF-16) 和 UTF-16 低半区 (Low-half zone of UTF-16)。

UTF-16 的一个 Code Unit,须要转换成 2 个字节的序列。因而,有大端序 (UTF-16 BE) 和小端序 (UTF-16 LE) 两种转换形式。

例如 0x0041 这个 Code Unit,应用 UTF-16 BE 最终会编码为 0x0041;应用 UTF-16 LE 最终会编码为 0x4100

UTF-8

UTF-8 将每个 Unicode Code Point 转换成 1 到 4 个 8 位长的 Code Unit。

UTF-8 是不定长的编码方案,应用前缀来标识 Code Unit 序列的长度。解码时,依据前缀,就晓得该将哪几个 Code Unit 组合在一起解析成一个 Code Point 了。

具体编码方式是:

Code Point 范畴Code Unit 个数每个 Code Unit 前缀示例 Code Point示例 Code Unit 序列
7 位以内 (0 - 0xEF)10b00b0zzz zzzz0b0zzz zzzz
8 到 11 位 (0x80 - 0x07FF)2第一个 0b110,剩下的 0b100b0yyy yyzz zzzz0b110y yyyy 10zz zzzz
12 到 16 位 (0x0800 - 0xFFFF)3第一个 0b1110,剩下的 0b100bxxxx yyyy yyzz zzzz0b1110 xxxx 10yy yyyy 10zz zzzz
17 到 21 位 (0x10000 - 10FFFF)4第一个 0b11110,剩下的 0b100b000w wwxx xxxx yyyy yyzz zzzz0b1111 0www 10xx xxxx 10yy yyyy 10zz zzzz

解码时,拿到每个 Code Unit 的前缀,就晓得这是对应第几个 Code Unit:

  • 前缀是 0b0,阐明这个 Code Point 是一个 Code Unit 组成
  • 前缀是 0b110,阐明这个 Code Point 是两个 Code Unit 组成,前面还会有 1 个 0b10 前缀的 Code Unit
  • 前缀是 0b1110,阐明这个 Code Point 是三个 Code Unit 组成,前面还会有 2 个 0b10 前缀的 Code Unit
  • 前缀是 0b11110,阐明这个 Code Point 是四个 Code Unit 组成,前面还会有 3 个 0b10 前缀的 Code Unit

UTF-8 的一个 Code Unit,刚好转换成 1 个字节,因而不须要思考字节序。

参考上表,对于 ASCII 范畴内的字符,应用 ASCII 和 UTF-8 编码的后果是一样的。所以 UTF-8 是 ASCII 的超集,应用 ASCII 编码的字节流也能够应用 UTF-8 解码。

UTF-8 与 UTF-16 比照

Code Point 范畴UTF-8 编码长度UTF-16 编码长度
7 位以内 (0x00 - 0xEF)12
8 到 11 位 (0x0080 - 0x07FF)22
12 到 16 位 (0x0800 - 0xFFFF)32
17 到 21 位 (0x10000 - 10FFFF)44

能够看出只有在 0x000xEF 范畴的字符,UTF-8 编码比 UTF-16 短;而在 0x0800 - 0xFFFF 范畴内,UTF-8 编码是比 UTF-16 长的。

而中文次要在 0x4E000x9FFF,如果写一篇文档,全都是中文,一个英文字母和符号都没有。那应用 UTF-8 编码,可能比 UTF-16 编码还要多占用一半的空间。


相干文章:

  • Unicode 标准化
  • Unicode 与编程语言
  • 字节程序标记
  • 字节序
  • Unicode 与 UCS