菜鸟笔记
提升您的技术认知

浅谈unicode编码

要想弄清楚char类型,就必须了解unicode编码机制。

在unicode出现之前,已经有了很多不同的标准,比如美国的ASCⅡ,西欧的ISO8859-1,中国的GB18030等。但是没有一个统一的编码机制,那么对于一个任意给定的字符值,在不同的编码方案下就可能对应不同的字母。设计Unicode编码的目的就是要解决这些问题。

这里我们将讲一下两种常用的unicode编码方式,分别为utf-8与utf-16。

对于utf-8来说,其编码占用的空间为1到4个字节。这就产生了一个最显而易见的问题,既然一个字符可以占用1个字节来表示,也可以占用多个字节,那怎么让计算机来进行识别呢?

utf-8的编码规则巧妙地解决了这个问题:

1.对于一个字节,将最高位设置为0,剩余的七位用来编码字符,而这种情况下与我们常见的ASCⅡ编码完全一致。

2.对于需要n个字节来编码字符的情况,第一个字节的前n位设为1,第n+1位设置为0,剩余字节的前两位均设置为10。这里以四个字节为例,则可以表示为11110xxx  10xxxxxx  10xxxxxx  10xxxxxx

在这里我们以汉字“汉”为例来演示utf-8是如何进行编码的。

首先,“汉”的码点是0x6c49(码点是指与一个编码表中某一个字符对应的代码值),转为2进制为0110  1100  0100  1001,根据上表的码点范围,我们可以得知按utf-8的方式进行编码“汉”字的编码格式为1110xxxx  10xxxxxx  10xxxxxx

将“汉”的二进制码点从低位填充,不足的补零,即可得到“汉”的utf-8编码为11100110  10110001  10001001

接下来,我们来看看utf-16编码方式,在Unicode标准中,码点采用十六进制书写,并且加上前缀U+,整个码点的范围为U+0000到U+10FFFF。

在了解 UTF-16 编码方式之前,先了解一下另外一个概念——“平面”。

 Unicode 可以看做是一本很厚的字典,她将全世界所有的字符定义在一个集合里。这么多的字符不是一次性定义的,而是分区定义。每个区可以存放 65536 个(2^16)字符,称为一个平面(plane)。目前,一共有 17 个(2^5)平面,也就是说,整个 Unicode 字符集的大小现在是 2^21。

最前面的 65536 个字符位,称为基本平面(简称 BMP ),它的码点范围是从 0 到 2^16-1,写成 16 进制就是从 U+0000 到 U+FFFF。所有最常见的字符都放在这个平面,这是 Unicode 最先定义和公布的一个平面。剩下的字符都放在辅助平面(简称 SMP ),码点范围从 U+010000 到 U+10FFFF。

基本了解了平面的概念后,再说回到 UTF-16。

它的编码规则很简单:基本平面的字符占用 2 个字节,辅助平面的字符占用 4 个字节。也就是说,UTF-16 的编码长度要么是 2 个字节(U+0000 到 U+FFFF),要么是 4 个字节(U+010000 到 U+10FFFF)。那么问题来了,当我们遇到两个字节时,到底是把这两个字节当作一个字符还是与后面的两个字节一起当作一个字符呢?

这里有一个很巧妙的地方,在基本平面内,从 U+D800 到 U+DFFF 是一个空段,即这些码点不对应任何字符。因此,这个空段可以用来映射辅助平面的字符。

辅助平面的字符位共有 2^20 个,因此表示这些字符至少需要 20 个二进制位。UTF-16 将这 20 个二进制位分成两半,前 10 位映射在 U+D800 到 U+DBFF,称为高位代理码点(H),后 10 位映射在 U+DC00 到 U+DFFF,称为低位代理码点(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

因此,当我们遇到两个字节,发现它的码点在 U+D800 到 U+DBFF 之间,就可以断定,紧跟在后面的两个字节的码点,应该在 U+DC00 到 U+DFFF 之间,这四个字节必须放在一起解读。

接下来让我们通过实例来看一下utf-16是如何进行编码的。

①、将辅助字符的码点值减去0x10000,得到一个20位长的二进制数

②、将得到的20位长二进制数拆分为高10位比特和低10位比特

③、20位长的高10位比特加上0xD800得到第一个代理码点,即高代理码点

④、20位长的低10位比特加上0xDC00得到第二个代理码点,即低代理码点

⑤、将得到的高代理码点和低代理码点组合成“代理对”,便得到了增补字符的UTF-16编码

⑥、示例:求辅助平面码点值为U+10437的UTF-16编码

将0x10437减去0x10000,得到0x00437,二进制为0000 0000 0100 0011 0111

将高10位,即0000 0000 01加上0xD800(二进制为1101 1000 0000 0000),得到高代理码点为:0xD801(二进制为1101 1000 0000 0001)

将低10位,即00 0011 0111加上0xDC00(二进制为1101 1100 0000 0000),得到低代理码点为:0xDC37(二进制为1101 1100 0011 0111)

将高代理码点和低代理码点组合成代理对得到UTF-16编码为0xD801 DC37