Unicode详解(附UTF-8、UTF-16和UTF-32)

Unicode

在百度百科上,对Unicode的介绍是这样的:

“ Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。”

Unicode源于一个很简单的想法:将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了。

它从0开始,为每个符号指定一个编号,这叫做"码点"(code point)比如,码点U+0639表示阿拉伯字母Ain,码点U+0041表示英语的大写字母A,码点U+4E25表示汉字

目前,Unicode的最新版本是7.0版,一共收入了109449个符号,其中的中日韩文字为74500个。但是,这么多字符也不是一次性定义的,Unicode将所有字符进行分区定义,每个区中有65536(2^16)个字符,即成为一个平面,目前一共有17个平面,也就是说,整个Unicode字符集的大小为1114112(2^21)个。

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

Unicode存在的问题

我们说Unicode只是一个字符集,只规定字符的二进制代码编号,没规定字符是如何进行存储,所以这也造成了一些问题。

比如,汉字的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。也就是说,这个符号至少需要2个字节来表示其它更大的符号。

因为需要至少2个字节来表示更大的符号,这就导致了两个问题,第一个是如何区别该编码是Unicode还是ASCII,计算机怎么知道该字符是2个字节还是3个字节甚至更多。第二个问题是,众所周知,英文字母只需要一个字节来进行编码,但是如果用2个字节3个字节甚至更多字节来表示这就会造成相应倍数的存储空间的增加,造成了存储空间上的极大浪费。

所以最后也出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式来表示Unicode,随着互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,同时也存在UTF-16以及UTF-32,以下我们会对其分别展开讲述。

UTF-8

UTF-8是一种变长的编码方法,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同。

再次强调一下,UTF-8与Unicode的关系是:UTF-8是Unicode的实现方式之一

UTF-8是一种边长编码方案,使得在存储上节省了许多空间,因此目前也受到了诸多欢迎,接下来,我们再讲一下UTF-8的编码规则。

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

  • 1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  • 2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围(十六进制) UTF-8编码方式(二进制)
0000 0000-0000 007F 0xxxxxxx
0000 0080-0000 07FF 110xxxxx 10xxxxxx
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。(百度百科)

跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

下面,还是以汉字为例,演示如何实现 UTF-8 编码。

的 Unicode 是4E25100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800 - 0000 FFFF),因此的 UTF-8 编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,从的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,的 UTF-8 编码是11100100 10111000 10100101,转换成十六进制就是E4B8A5

UTF-16 

UTF-16介于UTF-8和UTF-32之间,它同时结合了定长和边长两种编码方式的特点,

它的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。

因为UTF-16要不就是2个字节,要不就是4个字节,那么有个问题就是但我们遇到两个字节的时候,怎么知道它本身是一个字符,还是需要和另外两个字节放在一起解读呢?

在基本平面上有一段空白区间U+D800~U+DFFF,它没有用来定义字符编号,而是用来映射辅助平面的字符,也称为“代理区”

辅助平面的字符共有2^20个(为何是2^20个,待会解释),那么要对应辅助平面的字符就至少需要20个二进制位,UTF-16将这20位拆成两半,前10位映射在U+D800到U+DBFF(空间大小2^10),称为高位(H),后10位映射在U+DC00到U+DFFF(空间大小2^10),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。

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

Unicode码点转成UTF-16的时候,首先区分这是基本平面字符,还是辅助平面字符。如果是前者,直接将码点转为对应的十六进制形式,长度为两字节,若是后者,则需要进行上面所说的切分操作,具体看接下来讲的UTF-16的转码方式。

UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下(百度百科):

  • 如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。
  • 如果U≥0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

为什么U'可以被写成20个二进制位?Unicode的最大码位是0x10FFFF,减去0x10000后,U'的最大值是0xFFFFF,所以肯定可以用20个二进制位表示,这就是前面提到的辅助字符总共有2^20个的原因。例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。

按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

所以第一个WORD和第二个WORD的高6位二进制都是根据上面两个取值范围所得出的。

接下来顺便看看怎么由UTF-16编码推导Unicode编码。

如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:

1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到

1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111

即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。

至此UTF-16基本讲完。

UTF-32

UTF-32是最直观的编码方法,每个码点使用四个字节表示,字节内容一一对应码点。比如,码点0就用四个字节的0表示,码点597D就在前面加两个字节的0。

U+0000 = 0x0000 0000
U+597D = 0x0000 597D

UTF-32的优点在于,转换规则简单直观,查找效率高。缺点在于浪费空间,同样内容的英语文本,它会比ASCII编码大四倍。这个缺点很致命,导致实际上没有人使用这种编码方法,HTML 5标准就明文规定,网页不得编码成UTF-32。

总结

Unicode只是一个符号集,它只规定了符号的二进制代码,并没有规定这个二进制代码应该如何进行存储,简单的讲就是,Unicode为所有字符提供一个唯一的编号,然后UTF-8、UTF-16等只是将字符的Unicode编号编码成相应的二进制代码进行存储或运算,所以相同Unicode编号若用不同的编码方式进行编码,最后会产生不同的二进制代码,这就是有时候文件造成乱码的原因。

 

参考来源:

Unicode与JavaScript详解(阮一峰)

字符编码笔记:ASCII,Unicode 和 UTF-8(阮一峰)

Unicode(百度百科)

你可能感兴趣的:(编码)