什么是Unicode字符集?简单地说,它就是把全世界人类发明和使用的现有的所有字符进行了集中收集和逐一编码,这个过程就像把上学时老师把班里学生都叫到一起,统计总数后给每个学生分配一个唯一的学号一样。Unicode字符集里收录的字符可以是文字(如:‘α’、‘魍’等),也可以是符号(如:‘@’、‘$’),还可以是图形(如'☺'等)。
那它有什么用呢?它有两个重要的用途:
一是解决了人们和机器之间的字符交互问题。每个字符不再是一个个抽象的文字、符号或图形,而是变成了一个个的数字,每个数字对应一个唯一的字符,而每个字符也有一个唯一的数字,两者之间是一一对应关系,而且不同字符和不同数字都各不相同,避免了“重名重姓”问题。这里,提到的表示字符的数字,我们也称之为码点,后面我们还会详细介绍。
二是解决了不用语言国家字符集编码不统一的问题,提供了一个统一的编码方式,避免“各自为政,政出多门”的问题,方便相互之间的数据交流。
有了基本概念,那么我们看看Unicode字符集是如何实现对所有字符编码的。根据官网公布的Unicode 最新版本(9.0)介绍,Unicode字符集现在共包括128,172 个字符,可查看http://www.unicode.org/versions/Unicode9.0.0/ 。如此大量的字符,该如何编码?最笨的办法就是把所有字符列出来,然后一个一个编个号,但这样不利于查找,也不利于分类,更不利于进行存储空间优化编码(后面会介绍一些优化编码方案)。
那Unicode字符集怎么解决这个编码问题呢?它采用的是“分块编码”。按照国籍、地区、用途、功能等不同属性,把字符先进行分类,然后再根据每个小字符类的字符个数,确定一个个大小不同的码块,下面节选了几种字符及其对应的码点。
(节选)
0000..007F; Basic Latin(基本拉丁字母)
4E00..9FFF; CJK Unified Ideographs(CJK统一表意文字)
1D100..1D1FF; Musical Symbols(音乐符号)
100000..10FFFF; Supplementary Private Use Area-B(补充专用区域-B)
注意,“0000”、“007F”、“1D100”以及“100000”等,都是十六进制,这是每个字符在Unicode字符集中的编号,也就是相当于每个字符的“学号”。
可以看出,要表示一个字符,最长需要6位十六进制数,换算一下就是24位二进制数;而短的,比如基本拉丁字母,前面的“0”省去,只要2位十六进制(8位二进制数)就行了。
有了字符集,下面就要谈谈如何表示和使用这些字符(码点)了。毕竟,谁也不会闲了没事把字符编个号就为了练自己认字和数数的能力。最重要的当然是为了让不同信息受体间交换信息。
于是,就出现了UTF。所谓UTF是Unicode Transformation Format的缩写,意为Unicode转换格式。UTF具体分为3类,分别是UTF-32,UTF-16和UTF-8。
先看UTF-32。UTF-32是定长编码,也就是说每个字符的编码长度都是固定的,‘32‘是其所使用的二进制编码的位数,即:32位。但通常以字节数进行量化,所以32位对应的字节数为4字节。
我们的Unicode字符集每个字符的码点最长也就是24位,相当于3个字节,而UTF-32给了4个字节(32位)来表示,给了字符集非常大的扩展空间(有兴趣的童鞋可以算算32位二进制数最大可以表示多大的数,这个数基本就对应了可以表示多少字符)。
没这时间计算这些的童鞋你就简单理解为,UTF-32就是一个“运超大箱”的快递公司,不管你寄什么,它都统一拿装冰箱的盒子寄(觉得不够大的,自行脑补一个),保证能一次装下你要寄的东西。
它的优点是被表示的Unicode字符都是固定长度的,易于查找和解码;但缺点是表示常用字符时内存占用太大,本地存储利用率或传输效率太低。
UTF-16是变长编码,也就是说每个字符的编码长度是变化的,不是一成不变的。它的编码算法为:
假设字符的码点为c,c的形式为:“XXXXXX”,c对应着Unicode字符的码点,它即它的范围从“000000”至“10FFFF”。
- 若000000< c < 00FFFF,则UTF-16的编码结果就是码点c去掉前面8位“0”的后16位表示;
- 若010000< c < 10FFFF,则:
首先,c减去“010000”(十六进制),则c将从“XXXXXX”6位十六进制码降为“XXXXX”十六进制码(如:10FFFF - 010000 = 0FFFFF);
其次,由于1位十六进制码对应4位二进制码,则上述十六进制码可换算成20位二进制码
最后,以10位为一组,分别加上6位标识符,则可得到32位,即4字节二进制码,如下式所示:
110110bbbbbbbbbb 110111bbbbbbbbbb
式中,粗体字为另外加上的识别码,用于与“000000-00FFFF”编码后的单个2字节数进行区分;‘b’表示任意二进制数。
在UTF-16中,2字节是字符的基本表示单元,低码点的用2字节表示,高码点的拆开后用2个2字节表示。
还是拿快递公司的例子类比,UTF-16是家提供了一种“运中等箱子”的快递公司,中等箱子能装下的就直接寄,装不下的,做个标记,分两个箱子寄,收件人需要特别注意下标记,如果没有标记,直接就用,如果有,就把两个箱子东西取出来拼起来后再用。
UTF-8也是变长编码,它的编码算法与UTF-16并无本质区别,都是对Unicode进行分段,然后加上标识码,唯一的区别是分段更多。其算法如下:
- 字符码点在000000至00007F,由于最大值“7F”可用“111 1111” 7位二进制码表示,故编码为1个字节,即:
0bbbbbb- 字符码点在000080至0007FF,由于最大值“7FF”可用“111 1111 1111” 11位二进制码表示,故编码为2个字节,即:
110bbbbb 10bbbbbb (有效位为前5后6)
其中,标识位为110和10;‘b’表示任意二进制数。- 字符码点在000800至00FFFF,由于最大值“FFFF”可用“1111 1111 1111 1111” 16位二进制码表示,故编码为3个字节,即:
1110bbbb 10bbbbbb 10bbbbbb (有效位为前4中6后6)
其中,标识位为1110、10和10;‘b’表示任意二进制数。- 字符码点在010000至10FFFF,由于最大值“10FFFF”可用“1 0000 1111 1111 1111 1111” 21位二进制码表示,故编码为4个字节,即:
11110bbb 10bbbbbb 10bbbbbb 10bbbbbb (有效位为前3后全6)
其中,标识位为11110、10、10和10;‘b’表示任意二进制数。
在UTF-8中,1字节是字符的基本表示单元,最低的码点(000000-0000FF)用1字节表示,高的码点(000080-10FFFF)进一步分段,分别拆开为2个、3个和4个1字节。
可见,相比较而言,UTF-8是家只能“运小箱子”的快递公司,少数能装下的就用1个箱子运,不能装下的就拿2个、3个甚至4个来运。作为收件人,会非常辛苦的进行逐一判别,基本上都是需要拆箱组装后才能使用的(下面讲到也会有特例)。
当然,UTF-8在对于拉丁语系国家或者字符为主的信息传递和数据处理时,效率是非常高的,因为刚才Unicode字符集节选中提到的基本拉丁语范围刚好是0000..007F,在UTF-8中只要1个字节就够了。但是,对于中日韩(CJK,China-Japan-Korea)语系或字符为主的信息传递和数据处理时,效率就不那么好了,因为刚才节选的中日韩表意文字范围是4E00..9FFF,那在UTF-8中进行编解码时必须按照上面UTF-8算法的第3条进行处理,也就是要用3个字节来表示(还不如UTF-16的2字节),所以国内很多中文数据较多的网站一般也不会采用UTF-8来进行编码,但作为程序猿还是比较喜欢用这种的编码方式。
完。