字符编码:Unicode & UTF-16 & UTF-8
ASCII码
使用一个字节(8位),对128个字符进行编码;
最高位始终为0;
码数范围为0000_0000(0x00)
到0111_1111(0x7F)
;
Unicode
开始的编码设计
使用两个字节(16位),对65536个字符进行编码;
范围为0000_0000_0000_0000(0x0000)
到1111_1111_1111_1111(0xFFFF)
;
而0x0000 - 0x007F
对应的字符,与ASCII码保持一致;
最终的编码设计
由于世界上的字符,超过了65536个,所以开始只用两个字节的设计已经不足够了,需要扩展;
最终扩展如下:
-
基本多语言平面(BMP, Basic Multilingual Plane)
和开始的设计一致,用两个字节来编码,码数范围
0x0000 - 0xFFFF
;但是,在这个范围里,有预留
0xD800 - 0xDFFF
的码数,他们不代表任何字符,仅用于作为增补平面的代理对而存在; -
增补平面(SP, Supplementary Plane)
超出
BMP
所能表示的字符,改用如下范围:0x10000 - 0x10FFFF
来编码;Unicode编者认为这个范围已经足够全世界的字符编码了,因为这足够表示一百万多个字符了;
代理对(surrogate pair)
预留的0xD800 - 0xDFFF
,分为两部分:
- 高位
0xD800 - 0xDBFF
- 低位
0xDC00 - 0xDFFF
这样做的目的,是为了UTF-16编码方式;
一个高位加一个低位,共四个字节,定义了SP中的字符的UTF-16编码;
码点(code point)
Unicode编码中,一个字符所对应的码数,称为该字符的码点;
通常在计算机的字符和字符串中,使用\u码点
的形式来转义码点,来表示一个Unicode编码的码点所对应的字符;
UTF-16
请注意,Unicode编码的码点,是人为约定的对字符的编码方式;
但是计算机只认二进制,所以如何将Unicode定义的字符的码点,编码为计算机实际存储的二进制串,以及如何从一串二进制串,解码成Unicode定义的字符的码点,就是UTF-16
要做的事情;
UTF-16的16代表最小的编码单位是16位二进制串;
编码
分为两种情况:
-
BMP中的字符
直接用Unicode定义的码点作为UTF-16编码即可;
-
SP中的字符
使用两个16位二进制串进行编码,即采用四个字节来编码;
现在假设有一个字符,其Unicode定义的码点为
0xAAAAA
,对其进行如下操作:- u = 0xAAAAA - 0x10000;
- 将u写成二进制串:
yyyy_yyyy_yyxx_xxxx_xxxx
; - 则该字符的UTF-16编码为:
1101_10yy_yyyy_yyyy 1101_11xx_xxxx_xxxx
;
SP
的UTF-16编码的两个16位二进制串:第一个16位串的前六位固定是
1101_10
,结合yy
的范围(00 - 11
),即1101_1000 - 1101_1011
,此范围即是代理对的高位的前两位0xD8 - 0xDB
;第二个16位串的前六位固定是
1101_11
,结合xx
的范围00 - 11
,即1101_1100 - 1101_1111
,此范围即是代理对的低位的前两位的范围0xDC - 0xDF
;再结合各自后面八位二进制串的范围
0000_0000 - 1111_1111
,就可以得到各自完整的代理对;也就是说,
SP
的UTF-16的编码结果,即为高位+低位的四个字节的代理对;
解码
只要看一个16位二进制串的头八位,是否在代理对的范围即可;
-
不在代理对的范围
说明是
BMP
中的字符,直接对应Unicode码点找到对应的字符即可; -
在代理对的范围
说明是
SP
中的字符,再根据头六位确定好代理对的高低位,去除各自的前六位,组成20位二进制串,再加上
0x10000
即为Unicode定义的码点,即可找到对应的字符;
UTF-8
UTF-8是不同于UTF-16的另一种对Unicode的编解码方式;
不同之处就在于,UTF-8的8代表最小的编码单位是8位二进制串;
编码
UTF-8对码点的编码方式如下:
-
码点范围
0x0000 - 0x007F
UTF-8编码为二进制串
0xxx_xxxx
,与ASCII码保持一致,长度为1个字节; -
码点范围
0x0080 - 0x07FF
UTF-8编码为二进制串
110x_xxxx 10xx_xxxx
,长度为2个字节; -
码点范围
0x0800 - 0xFFFF
UTF-8编码为二进制串
1110_xxxx 10xx_xxxx 10xx_xxxx
,长度为3个字节; -
码点范围
0x10000 - 0x10FFFF
UTF-8编码为二进制串
1111_0xxx 10xx_xxxx 10xx_xxxx 10xx_xxxx
,长度为4个字节;
假设现在有一个字符,码点在范围0x0800 - 0xFFFF
中:
- 将其码点写成二进制串:
xxxx_yyyy yyzz_zzzz
; - 则UTF-8编码的第一个字节为
1110_xxxx
; - 第二个字节为
10yy_yyyy
; - 第三个字节为
10zz_zzzz
;
解码
只要看第一个字节的首位即可:
-
首位为0
说明在码点范围
0x0000 - 0x007F
,直接对应Unicode码点找到对应的字符即可; -
首位为1,再看从首位开始,遇到第一个0结束,一共有几个1
- 两个1,说明UTF-8编码长度为2个字节
- 三个1,说明UTF-8编码长度为3个字节
- 四个1,说明UTF-8编码长度为4个字节
- 去除对应字节的固定位,组合为一个二进制串,找到对应Unicode码点的字符即可;
代码单元(code unit)
不同的UTF编码,所对应的编码单位的长度不同;
UTF-16的编码单位的长度为16位二进制;
UTF-8的编码单位的长度为8位二进制;
这个编码单位称为代码单元;
比如对于UTF-16的编码:
在BMP
中,一个字符所对应的UTF-16的16位二进制串,称为该字符的代码单元;
而在SP
中,一个字符所对应的UTF-16的两个16位二进制串,称为该字符的一对代码单元;
而对于UTF-8的编码:
在码点范围0x0000 - 0x007F
中,一个字符所对应的UTF-8的4个字节,称为该字符的4个代码单元;
在码点范围0x0080 - 0x07FF
中,一个字符所对应的UTF-8的4个字节,称为该字符的4个代码单元;
在码点范围0x0800 - 0xFFFF
中,一个字符所对应的UTF-8的4个字节,称为该字符的4个代码单元;
在码点范围0x10000 - 0x10FFFF
中,一个字符所对应的UTF-8的4个字节,称为该字符的4个代码单元;
也就是说,随着UTF编码形式的不同,同一个字符的码点,会有不同个数的代码单元;