Unicode字符编码—就这么回事

Unicode全称应该是unity code ,翻译过来就是统一码。意思嘛,就是统一地球上所有字符的编码。

编码是什么东西呢?就是把一个东西用一个数字来表示(广义上的概念不是这样,但这里可以这么理解)。对字符的编码,就是把一个字符用一个数字来表示。

最常见的是Ascii编码,学过计算机的大概都知道。字符“a”也就是小写的a,用的是数字97表示,字符“1”用的是49表示。完整的Ascii表有256个字符,分别用0-255来表示。

但世界那么大,语言那么多,区区256怎么能表达完世界上所有的字符呢?所以啊,Unicode就是用来统一世界上所有字符的编码方式了。说到底,也就是把255这个最大值,  放大到 111 4112(0x10FFFF = 17 * (2^16))这个最大值。

100多万的数字用来表示地球上的字符,在没有发现外星人之前,应该是够用了的。汉字也就几万而已,拉丁字母都才26个。像中日韩这样有很多字符的语言是不算多的。

在 Unicode 里面,这些数字有一个概念名词——码位。一个码位代表一个唯一的字符。

码位不会用10进制来表示,而是用16进制表示,一般书写某个字符的码位,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。比如,“字”的码位是U+5B57。

Unicode把65536(2^16)作为一个范围的大小,将111 4112分成了17份,这每一份又有一个概念名词,叫平面。一共有17个平面(65536 * 17 = 1114112)。其中第一个平面叫基本多文种平面,BMP(BasicMultilingual Plane),或称第零平面(Plane 0),是Unicode中的第一个编码区段。

在BMP中,U+4e00到U+9fa5是汉字的码位范围,共20902个,这只是汉字的一部分而已。因为这也没办法啊,汉字的数量比一个平面的最大值65536还多。所以在BMP中只编码了一部分常用的汉字而已,其他生僻字,都编码在了其他的平面中。一般场景下,几千个汉字都已经足够使用,所以也不必太纠结。

65536的16进制是0xFFFF,所以用两个字节就可以表示完第一个平面的所有字符了。

这个时候,你是不是以为,在一个文本文件中,如果我们只保存一个字,比如上面提到的“字”,码位是U+5B57。那么这个文件的二进制流就是01011011 01010111了?呵呵,都是大人,我们就不天真了嘛。没这么简单,但也不复杂。

如果直接存储字符的码位,从理论上来说,肯定是可以的,操作也非常方便。但是站在实用的角度来看,这就不行了,因为太浪费空间了。

举个例子,一个文本文件,里面全部都是用英语来表达书写的(写代码的文件不一般都这样嘛),那么这个文件里所有字符的码位都是在0-127之间,Unicode兼容一般的Ascii编码,也就是第一个字节。大写字母A的码位是U+0061。十进制表示的码位为127的那个字符的十六进制码位为U+00FF。

看到没有,前面始终有两个0,因此,每存储一个字符,都会白白浪费一个字节的空间,因为每个字符的二进制都是00000000XXXXXXXX啊。如果去掉前面这些0,一个文本文件就能减少一半的存储空间,多么美好的事。如果不去掉0,在这个倡导节约型社会的时代,我们难道不觉得可耻吗?

所以呢,我们还需要对码位再次进行编码。。。。别晕,确实是再次编码。但和上面的编码概念是不一样,上面可以看成是对字符集的编码方式。而接下来要说的,是对码位的编码方式,他们的方式是完全不同的。


对字符集的,是将某个字符编码对应到某个数字;

而对码位的,是将码位数值编码对应到某个二进制串。


看到这里千万要挺下去,因为这里懂了后,一切都明朗了。

Unicode是将字符编码到码位,而UTF-8、UTF-16或UTF-32等是将码位编码到二进制串。UTF全称是Unicode Transformation Format,也就是Unicode转换方式。

还是汉字字符“字”,码位是U+5B57。

如果采用UTF-32编码方式,那么二进制串是“00000000 00000000 01011011 01010111”,采用了4个字节,32位二进制来表示了,所以是UTF-32。

也就是说UTF-32的编码规则就是直接把码位转换成2进制存储,简单又粗暴。

如果采用现在最通用的编码方式UTF-8,那么“字”的二进制串是“11100101 10101101 10010111”。只用了三个字节,24位二进制就表示了。

这里怎么不是采用8位二进制来表示了?骚年,不要这么推理嘛。谁说这个8就代表只用8位二进制来表示了。为什么叫8呢,我也不是太清楚,得问提出这种方法的人了。

而对于UTF-16的方式,不同的就是编码方式不一样,所以出来的二进制串也不一样。

先说说UTF-8是按照什么样的规则来编码的吧。最大的特点就是不同范围的码位,用不同的二进制规则来编码。也就是变长的编码方式。


Unicode符号范围 | UTF-8编码方式

(十六进制) | (二进制)

——————–+———————————————

00000000 - 0000007F | 0xxxxxxx

00000080 - 000007FF | 110xxxxx 10xxxxxx

00000800 - 0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx

00010000 - 0010FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的编码规则简单到只有二条:

1)对于单字节的码位,字节的第一位设为0,后面7位为这个符号的Unicode码位。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个字符的Unicode码位的二进制。

如果觉得16进制的码位范围不太好理解,可以换成10进制的。


码位为0 - 127之间的字符,就编码为1个字节。字节的首位定为0,当程序读到这个字节时,发现第一位是0,就知道这个字符是1个字节编码的。

码位为128 – 2048之间的字符,编码为2个字节。

码位为2049 至 65536之间的字符,编码为3个字节。

码位为65537 至 2097152(但注意码位最大值是1114112)之间的字符,编码为4个字节。

如何计算这些界限值?在编码的几个字节中,剩余的x的个数为n的话,界限值就是2的n次方。

 

再来聊聊UTF-16的编码方式吧。


和UTF-8类似,也是分了范围。不同码位范围,用不同的编码方式。但只有两个范围:


1.  码位在第一个平面内的字符,直接将码位转换为2个字节的二进制串,储存起来。比如,还是字符“字”,就直接就直接将码位U+5B57,转换成01011011 01010111 储存起来,一共有两个字节。


2.  不是在第一个平面内的,也就是在其他16个平面内的字符,先将码位转换为20位二进制。然后依次放进1101 10XX XXXX XXXX 1101 10XX XXXX XXXX这里面的X中。二进制规律也简单,四个字节分成前后两段,每段都以1101 10开头,其他的都是用来填充的二进制位了。比如,某个字符的码位是U+12345,转换成20位二进制就是0001 0010 0011 0100 0101,用UTF-16编码,出来就是1101 1000 0100 1000 1101 10110100 0101 ,一共有四个字节。


对于第一个范围的规则,很简单的将码位转换为二进制而已,不必多说。

但是,对于第二种方式,有些同学就有疑问了。计算机怎么知道是两个字节解释成一个字符,还是四个字节一起解释成字符呢?

难道是以2个字节,是否以1101 10开头来判断的?但是这个形式开头的二进制串,不会解释成第一个平面内的字符吗?

还真的是这样子的。以1101 10开头的两个字节形成的二进制串,不会被解释成第一个平面内的字符。因为在Unicode里,1101 1000 0000 0000到1101 1011 1111 1111之间(也就是码位U+D800到U+DBFF之间),没有编码任何的字符,这区间是一个空段(其实空段的区间是U+D800到DFFF,这只是其中一部分)。

UTF-16就利用了这个特点,将其他16个平面内的字符就这样编码了。

最终规则就是,字符根据Unicode规则,编码成码位,然后码位根据UTF-8、UTF-16、UTF-32等规则,编码成二进制串。最终存储的,就是这个二进制串了。

                </div>

猜你喜欢

转载自blog.csdn.net/jyxmust/article/details/79351251