UTF-8
文章假设你已经熟知计算机的基本单位( 1 字节
= 8 位二进制
= 2 位十六进制
),且对 Unicode 有清楚的理解。
描述
网络上已经有很多关于 UTF-8 的描述版本了,这里尝试从另一个角度对 UTF-8 进行描述。
Unicode 使用「 1个16进制的字节+2个16进制字节 」(从 0000
到 10 FFFF
)的编码方式与全球所有语言的字符进行对应,但这会大大提高浪费计算机储存空间的概率。比如很多英文字母的 Unicode 码是 00xx
,单单一个字母就浪费了2个单位( 00
)的储存位,这样一篇英文文章至少会浪费一半的储存空间。为了合理利用计算储存空间,UTF-8 应运而生。
之前的计算机只能死脑筋地认为 i 个字节表示一个字符,这会导致新字符的兼容性问题(例如后来加入新的字符需要 i+1 个字节表示)以及前文提及的储存空间浪费问题。
UTF-8 通过一定的规则,使得字符转译生成的编码的长度不再固定。除了本身的 Unicode 编码之外,每个字符还会嵌入自己的编码长度信息。UTF-8 的引入增加了字符的编码长度的可拓展性,允许将来新的字符的引入,也同时避免了储存空间的浪费。
编码规则
UTF-8 的编码规则只有2条:
- 对于单字节的 UTF-8 编码(二进制),字节的第 1 位设为
0
,后面 7 位为这个字符的 Unicode 码。因此对于英文字母,UTF-8 编码和 ASCII 码是相同的。- 对于 n 字节(n > 1)的 UTF-8 编码(二进制),第 1 个字节(二进制)的前 n 位都设为1,第 n+1 位设为 0,后面字节的前 2 位一律设为10。剩下的没有提及的二进制位,全部为这个字符的 Unicode 码。
上面的规则描述可用下面的表格来诠释(字母 x 表示可填编码的位):
字节数 | UTF-8 编码结构(二进制) | 可填写的编码范围(二进制) |
---|---|---|
1 | 0xxxxxxx | (0)000 0000 ~ (0)111 1111 |
2 | 110xxxxx 10xxxxxx | (0)000 0000 0000 ~ (0)111 1111 1111 |
3 | 1110xxxx 10xxxxxx 10xxxxxx | 0000 0000 0000 0000 ~ 1111 1111 1111 1111 |
4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | (000)0 0000 0000 0000 0000 0000 ~ (000)1 1111 1111 1111 1111 1111 |
… | … | … |
这样的规则解决了2个问题:计算机如何识别字节长度不一的字符、UTF-8 如何与 Unicode 一一对应
计算机如何识别字节长度不一的字符
UTF-8 是允许有不同字节数的字符存在的,那么计算机编译器是如何从类似111001101011010110110111111010011001100010010100
这样的0/1编码中识别出一个个字符的?
计算机编译器在编译第 1 个字节时,判断这个字节的第 1 位是 0
还是 1
: 若是 0
,则这个字节就表示一个字符;若是 1
,则计算从这个 1
开始(包含)到 0
结束,有 n 个 1
,那么从这个字节开始(包含)的 n 个字节就表示一个字符。
当识别出第 1 个字符后,以此类推,继而判断第 2 个字符的字节数,识别出第 2 个字符;再识别第 3 个字符……直到最后一个字符,这样就实现了所有字符的识别(解码)过程。
比如上面的 1110010110111100100000000101000001100001011100100111010001111001
可以分割为6个字符:
| 11100101 10111100 10000000
| 01010000
| 01100001
| 01110010
| 01110100
| 01111001
|
UTF-8 如何与 Unicode 一一对应
根据 UTF-8 的规则,字符在有限的字节编码中,抛去标示声明字节的编码位,剩下的就是可编写的——见上面表格“UTF-8 编码结构(二进制)”中字母“x”表示的二进制位,就是留给 Unicode 码填入的,这就实现 UTF-8 与 Unicode 的一一对应了。
但是这些可编写的二进制位的编码范围是有限的,如 1 个字节的 UTF-8 编码可编写 27 2 7 (即128) 个 Unicode 码,即可编写从 0000
到 007F
的 Unicode 码。
下表总结了不同字节数 UTF-8 码可编码的 Unicode 范围(字母 x 表示可用编码的位):
UTF-8 编码结构(二进制) | 可编写 Unicode 位数(二进制) | 可编写 Unicode 编码范围(十六进制) |
---|---|---|
0xxxxxxx | 0~7位 | 0000 0000 ~ 0000 007F |
110xxxxx 10xxxxxx | 8~11位 | 0000 0080 ~ 0000 07FF |
1110xxxx 10xxxxxx 10xxxxxx | 12~16位 | 0000 0800 ~ 0000 FFFF |
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx | 17~21位 | 0001 0000 ~ 0010 FFFF |
… | … | … |
以中文字符 海
为例,将其 Unicode 码 6D77
编译为 UTF-8 码步骤:
- 将十六进制
6D77
转换为二进制1101101 01110111
,位数是15位; - 根据规则,3 个字节的 UTF-8 码可编译 12~16 位 Unicode 码(见上表),最适合编译当前字符;
- 将
1101101 01110111
依次填入 3 字节的 UTF-8 码可编辑位(多余的最高位补上0
),得到11100110 10110101 10110111
,这个就是 UTF-8 码了; - 如果愿意,可以将其转换为 16 进制
E6 B5 B7
。
值得注意的是,虽然超过 3 个字节的 UTF-8 码的可编写位数也能够填入 15 位的1101101 01110111
、多余的高位补上0
,但这样做编译器不会同等识别出来。
笔者猜测是因为这样不利于节约储存空间,且已经有最优解——采用 3 个字节的 UTF-8 码编译,所以 UTF-8 干脆不做重复的字符编码了。因此在 UTF-8 中,推荐以最短字节数表示字符,不允许冗余的字节。这也是上面表格中“可编写 Unicode 编码范围(十六进制)”的起点不和 UTF-8 可编辑位范围一致的原因( 1 字节除外)。
从这点来看,UTF-8 的变长字节优势凸显,但也存在部分编码留白的现象。
参考:字符编码详解
还没有评论,来说两句吧...