字节(Byte)
1字节代表8比特(bit),例如 00001111
为1字节。
字符
任何一个文字或符号都是一个字符,但所占的字节不一定相同。除了常见的字母汉字还有特殊字符例如货币字符¥等。
字符集
字符的集合就叫字符集。
字符编码(charset encoding,简写为 encoding)
定义字符集中的字符如何编码为特定的二进制数。
字符集和字符编码一般一一对应,GBK 字符集对应 GBK 编码,ASCii 字符集对应 ASCii 编码,但 Unicode 字符集有 UTF-8、UTF-16 和 UTF-32 三种编码方式。
Unicode
Unicode 编码定义了世界上几乎所有的字符。
码点/码位(code point)
码点是编码中为某个字符设定的数值,具有唯一性和一一对应性。码点只定义了一个字符对应的数值,没有定义这个数值是怎么存储的,这个由具体的编码方式决定。
U+XXXXXX 是码点的表示形式,X 代表一个十六进制数字,u 后面可以有4-6位,不足4位往前补0,所以码点的取值范围为 U+0000 ~ U+10FFFF,但实际 unicode 至今才扩展到二进制21位,而且21位代表的1114112个码点中并不是都代表着一个字符,只有大约10%的空间被使用。
例如字符A的ASCII码是65,65转换为16进制为41,所以字符A的码点表示就是 U+0041。
可以在这个网址查询字符的的unicode相关信息:Unicode Character Search
码元(code unit)
码点经过映射后得到的二进制串的转换格式单位称为码元,码点就是一串二进制数,码元就是切分这个二进制数的方法。
例如一个字符的码点二进制表示有n字节(8*n位),其码元为8位,那么其码元就有n个。
平面
为了更好管理这么多码点,把每65536个码点作为一个平面,总共17个平面。
BMP(Basic Multilingual Plane)
这些平面编号从0开始,第一个平面就是 BMP(基本多语言平面),也叫 Plane 0,它的码点范围是 U+0000 ~ U+FFFF,平常用的绝大多数字符都落在这个平面内。
UTF-16 只需要用两字节编码此平面内的字符。
SP(Supplementary Planes)
后续的16个平面称为 SP,这些码点超出 U+FFFF,UTF-16 只需要用四字节编码此平面内的字符。
编码方案
Unicode 只是给每个字符分配了个码点,但是码点转换为二进制存储时可能占1-4个字节,产生了定界问题:如何确定用几个字节表示一个字符。不同的解决方法对应不同的编码方案。
UTF-32
UTF-32 就是说它的码元是32位,每32位读一次码点,而码点最长才21位,所以每个 UTF-32 值都能表示一个码点。固定用4 byte 表示一个字符。
UTF-16
UTF-16 就是说它的码元是16位,每16位读一次码点,获取码点的前16位数字,直到读取完成。用2 byte 或者4 byte 表示一个字符。
代理区
Unicode 标准规定U+D800 ~ U+DFFF的值不对应于任何字符,UTF-16利用这个空白区域进行编码的转换。UTF-16将代理区进一步划分成两部分,0xD800 ~ 0xDBFF 分区称为高半代理,0xDC00 ~ 0xDFFF 分区称为低半代理。这两个区组成一个二维的表格,共有 $1024*1024=2^{10}*2^{10}=2^{4}*2^{16}=16*65536$,恰好可以表示16个 SP 中的所有字符。
因此将 Unicode 分为了两个范围,通过不同的方式进行存储:
Unicode 范围 | UTF-16 编码方式 |
---|---|
U+0000 ~ U+FFFF | 2 byte 存储,编码后等于 unicode 值。 |
U+100000 ~ U+10FFFF | 4 byte 存储,将 unicode 码点减去 0x010000,得到20 bit 的值,分为高10位和低10位,高10位加上 0xD800 得到高位代理也叫前导代理;低10位加上 0xDC00 得到低位代理也叫后尾代理。两个代理组合在一起就是最终在内存中存储的值。 |
UTF-8
用1~4 byte 编码 Unicode 字符
Unicode 范围 | UTF-8 编码方式 |
---|---|
U+0000 ~ U+007F | 0xxxxxxx |
U+0080 ~ U+07FF | 110xxxxx 10xxxxxx |
U+0800 ~ U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+010000 ~ U+10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
将 unicode 码点转换为二进制后,按照对应的范围从后往前截取出表中空出来的位数,依次从后往前填充进去,空的位数填0,就得到在内存中存储的值。
字符“严”的 Unicode 是4E25(100111000100101),根据上表可以发现 4E25 处于第三行的范围,因此从“严”的最后一个二进制位开始,依次从后向前填入格式中的 x,多出的位补0。这样就得到了“严”的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5。
Emoji 是一个字符吗?
很多 Emoji 其实不是一个 Unicode 码元能表示的,比如❤️,就算转换成了 UTF-32 也占8个字节,是2个字符,❤️其实是由🖤和一个样式控制符组成的,当文字渲染引擎读到这样的字符组合时就会去 Emoji 字体库找❤️来渲染,所以对于这类 Emoji 处理起来也要特殊考虑。
更特殊的是👩❤️💋👩,这是由6个字符组成的,里面用到了零宽度连字符将几个表情连接起来,最后就能作为一个字符渲染了。
Emoji 详细说明可以看这篇文章:【拓展】谈谈字符编码:Unicode编码与emoji表情编码 - 腾讯云开发者社区-腾讯云 (tencent.com)