Unicode、UTF-8和UTF-16

??先说一下字符集,顾名思义,就是各种文字和符号的集合。常见的字符集包括Unicode、ASCII,GBK等。小可爱们,开门即遇坑,在这里一定要注意,UTF-8和UTF-16不是字符集哦!那它俩是啥,又和Unicode有什么区别呢?请继续看下去。
??众所周知,Unicode被称为万国码,是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。所以说每一个字符在Unicode中都有唯一的编码。这些编码值就被称为码点。同时,Unicode标准中还指出码点由十六进制书写。在ASCII中,一个ASCII码占用8个二进制位,即一个字节。那么一个Unicode码点占几个字节呢?嘿嘿嘿,其实这样问本身是有问题的。Unicode只规定了字符的二进制表示方式,并没有指出这些二进制数据如何在计算机中存储。为什么呢,我们都知道ASCII字符集中’A’是0100 0001、’a’是0110 0001,即一个ASCII占据1个字节。但在Unicode中则不同了,举个例子。’严’这个字符在Unicode中的码点是4E25,转换成二进制数是100 1110 0010 0101,’A’的码点为0041,转化为二进制数是0100 0001。它俩的长度竟然相差这么大!也就是说如果按2字节存储,A的二进制数会有9位无效的0,但要是按照长度选取最小的字节长度来存储,计算机又怎么知道什么时候该读1字节,什么时候该读2字节呢?这时候UTF-8、UTF-16就该上场了。它们被称作编码规则,能实现Unicode字符集可变长度的编码,也就是说计算机中实际存储的是编码后的Unicode值。由于常用的是UTF-8,所以先解释UTF-8与Unicode之间的编码。

  • 对于单字节的符号。字节的第一位设为0,后面7位为这个符号的 Unicode码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  • 对于多字节的符号。最高位所在字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的用Unicode 码右端对齐填充。
Unicode码点(十六进制) ?????UTF-8编码(二进制)
000000-00007F 0xxxxxxx
000080-0007FF 110xxxxx 10xxxxxx
000800-00FFFF 1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
??例:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是: 0110 1100 0100 1001,右端对齐填充模板,得到:11100110 10110001 10001001,即E6 B1 89。
??既然UTF-8是最常使用的编码方式,那么为什么我们还要讨论UTF-16呢?因为Java采用的是UTF-16编码。哈哈哈,是不是很interesting。这时候我们再从UTF-8来看看Unicode的表示范围,即:[000000, 10FFFF],共可表示17 * 65535个字符。17指17的编码平面。UTF-16的编码规则如下(U代指Uniocde码点):
  • 如果U < 0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。
  • 如果U > 0xFFFF,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
【Unicode、UTF-8和UTF-16】??看到这,UTF-16编码Unicode的方式就讲完了,但为什么这样做就能编码Uniocde字符集呢?我们接着看下去。
??首先,U的最大值为0x10FFFF,减去0x10000后,U'的最大值是0xFFFFF,所以肯定可以用20个二进制位表示。例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。右端对齐替代模板,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。
??按照上述规则, 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]。恰好Unicode标准规定:基本多语言平面([U+0000, U+FFFF])内,[U+D800, U+DFFF]里共2048个值不对应于任何字符,为代理区。也就是UTF-16正好利用了代理区来区分什么时候读4个字节,什么时候读2个字节。所以Java核心技术卷中强烈不建议使用char类型。因为一个char只代表一个WORD。
??综上,在一波又一波骚操作之后字符集、Unicode、UTF-8,UTF-16就不那么神秘了。

    推荐阅读