unicode编码的秘密

来源:互联网 发布:越狱工具for mac 编辑:程序博客网 时间:2024/05/11 03:29

概念上的澄清:如果有人问你知道不知道unicode呢?你可能会说,我知道,不就是“统一字符编码标准”嘛。对的,你回答的没错,但别人又问你,utf-8是什么呢?你可能会说,utf-8就是unicode。这个回答就不够准确了。UnicodeASCIIGB2312一样,是一种编码标准。平常更随意的说话是简称为“编码”。这里“编码”的概念,实际上包括了两方面的含义,其一是指该编码所对应的字符集;另一方面是指具体的编码方案,也就是我们通常所说的内码格式。所谓字符集就是指该编码方案包括哪些字符,比如ASCII编码一共包括128个字符,其中有英文大小写字母、数字、标点符号以及一些控制字符。而编码方案是指如何把这128个字符用字节码表示出来,比如说,是把英文字母“A”对应为第1号字符,还是对应为第N号字符呢。在ASCII编码中把字母“A”对应为第65号字符,十六进制表示就是 0x41

首先来说说最通用的ASCII编码(美国资讯交换标准码)。ASCII编码的128个字符正好可以被一个字节的前七个bit位来表示,而最高的第八个bit位置为0就可以了。几乎现在的所有的编码标准都是和ASCII编码相兼容的。所谓兼容,就是指,新的编码只是扩充ASCII编码,但一个字节的前128个字符都是和ASCII编码完全一致的。我们可以看出,ASCII编码针对英语来讲是足够了的,但它无法表示像中文这样的庞大的字符系统,所以各个国家都针对自己民族语言的特点来扩充ASCII编码。像德语这样的语言,字符元素的数量和英语差不多,所以完全可利用一个字节的剩下的128个字符位置来给自己的语言编码。但对东方语言,事情就没那么简单了,最为庞大的就是汉语了,光简体字形就六、七千个,再加上繁体字、生辟字、古汉字、少数民族的字形以及一些常用的符号、标志啦,总数是在万到十万的量级上。而单字节的最大容量只有256,显然是不能满足汉语的要求的。

双字节的容量有多大呢?256乘以256等于65536,双字节有六万多的空间,这足以满足我们日常的需求了。我们最常用的GB2312GBK编码标准就都是支持双字节的编码标准,当然,它们也是和ASCII码兼容的。

对于每一种编码标准我们都要从字符集和编码方案两个方面去了解。GB2312的字符集,其中除了与ASCII码兼容的那些字符外,再就是包括I级汉字3755字、II级汉字3008字,共计6,763个汉字,另外还有一些符号和标志字符。GB2312的编码方案有两种,一种是用得最为广泛的,就是EUC变字节编码方式。可以用单字节也可以用双字节,为了能区分单字节字符和双字节字符的界限,要求双字节的第一个字节的最高位必须置为1,以就是说,当软件读到一个字节,发现它的值大于128了,就认为它不是一个与ASCII码兼容的字符了,而是一个双字节字符的第一个字节,要连同下一个字节,一起被解读为一个字符。变字节方式的编码方案做到了对单字节编码的扩展,并且可与单字节编码相兼容,但有一个缺点就是在字数统计上变得困难了。另一种编码方案是HZ编码方式,由于现在用的非常少,所以就不介绍了。

GB2312中的汉字全是简体汉字,为了能应用繁体汉字,就有了GB12345标准。GB12345标准的字符集是与GB2312字符集相对应的汉字的繁体字型,当然还有一些增加的字符。它的编码方案与GB2312完全相同,也就是说,同样的一个双字节字符,比如0xB9FA,在GB2312中表示的是“国”字,而在GB12345中表示的就是“國”字。

这样表示繁体字有一个弱点,就是无法同时显示简体和繁体字,因为你不能让软件对这个字符用这个编码标准,而对另一个字符用另一编码标准,即使软件支持这样的功能,这一切也太过复杂了。而且随着信息的国际化,越来越多的要求把多种语言集成在一起,比如说一份文档中,不仅含有英文、简体中文和繁体中文,还包括日语和韩语文本。基于这些的考虑,就有了GBK编码标准,GBK标准的字符集共包含20902个汉字、日语中的平假、片假名、日语中的汉字、韩语中的字型以及一些数学符号、标志符号等等。两万多的汉字,基本上覆盖了所有的简体和繁体汉字,以及大部分的生辟字等。GBK的编码方案也是变字节的,并且和GB2312完全兼容,只是在GB2312双字节区域中扩展了编码范围,比如说,GB2312中双字节的第一个字节只能在0xA1~0xFE范围内,而GBK则扩展为在0x81~0xFE范围内。为了与GB2312兼容,在排定了GB2312字符集的位置后,再在扩展的位置上指定剩下的字符。所以原有的GB2312的文档在GBK中都是能正确显示的。

故事到这里应该是已经很完美了,但是还差一些,让我们继续我们的编码探秘之旅。随着计算机在各个领域中的使用,大量的特殊字符涌现出来了,比如说偏微分符号、商标注册符号、五线谱的高音谱号。另一方面就是在学术中要求对古语言字型和各少数民族字型的支持。出于以上这些考虑,就要求有更庞大的编码标准,于是乎双字节的65536个空间也显得不够用了。只能要求更多的字节来容纳这些新的字符。中国恰好是一个历史悠久又多民族的国家,为了能把古汉语的字型以及蒙语、藏语等多民族的语言容纳进来,于是出台了GB18030标准,该字符集光汉字就包括了27533个,还有其它的字符等等。GB18030的编码方案也是变字节的,它支持四个字节的字符。GB18030GBK是兼容的,GB18030的双字节汉字对应的就是GBK字符集中的所有汉字,其余的6631个汉字是用四字节编码的。

上面的所有只是中国一脉的编码发展,而别的国家的编码方案也是纷繁的,这样的话,在软件的国际化上将变得非常困难。国际标准组织为各国的各种编码标准定义了一个编号,叫做编码页CPCode Page),比如说,GBKCP936GB2312CP20936GB18030CP54936BIG5CP950,我们平常用的英文环境是ANSI-Latin I,其编码页是CP1252。不同的编码标准一般情况是不互相兼容的,比如GBKBIG5。而不同的编码标准的字符集也是不一样的。这在文档的共享和软件的设计上都会产生困难,所以大家需要一种公共的编码方案来整合所有的编码标准,这就是——Unicode

同样,Unicode规范也分别定义了它的字符集和编码方案。Unicode的字符集很庞大,基本上包括了世界各地的语言和各个领域中的符号,而且随着Unicode标准的更新,更多的字符将被包括进来。单从汉语上来讲,在Unicode2.0时,它支持的中文与GBK中的完全一样,共计20902个汉字;而Unicode3.0,在扩展A区中,又添加了6582个汉字,共计27496个汉字,这与GB18030的容量基本上差不多了。而在Unicode3.1中,还会在扩展B区中添加更多的字符支持。Unicode字符集的定义是抽象的,它只是指定了一个序列,比如说:

U+0020 : SPACE

U+0041 : LATIN CAPITAL LETTER A

U+0042 : LATIN CAPITAL LETTER B

其中的U+0020表示十六进制的第20位的字符是一个叫SPACE的字符,也就是我们通常说的空格符。U+表示这是一个Unicode字符集序列号。这与该字符的实际编码方案是没关系的,完全可以把这个空格符编成任何一个字节形式,而不一定要与0x20相对应。这正是编码方案所要定义的。

       Unicode的编码方案有很多种,而且不同种在各自应用的环境有各自便利的地方。我们最常见的utf-8就是Unicode的一种编码方案。如果说一个文件是unicode的,很可能是指utf-16这种编码方案。

       我们从Unicode字符集定义的方式,可以相出一种很显然的编码方案,就是直接利用该字符在字符集中的序号来编码,比如那个空格就是编码为0x20,这样很好,但如果有一个序号是U+2020的字符呢?它就无法与这个空格符区分开了。utf-16编码就是完全用双字节来编码的,即使简单的空格符也要被编码成0x0020。这样每个Unicode字符都是两个字节了,很方便字数的统计。当然你可能会说,反正是两个字节,为什么不倒过来编呢,比如说,把空格符编成0x2000,而事实上,一种叫做Unicode Big-Endianutf-16BE)的编码方案正是这么做的,当然这么做的真实原因是为了方便某种平台对这种编码的读取。于是utf-16也可以称为Unicode Little-Endianutf-16LE)。为了方便软件能认出文本采用的是哪种编码方案,可以在文本的最前面加上该编码的标志字节,utf-16LE的是0xFF 0xFEutf-16BE的是0xFE 0xFFWindows XP下的记事本程序是支持Unicode的,我们可以把一个文件以Unicode方式保存。然后用UEdit以二进制方式打开,就会发现文本的最前面两个字节是编码标志字节。

如果我们的文档保存的大部分信息是英文信息,其中只有很少的中文,那么采用utf-16编码方案就很不经济了,因为每个英文字母都是用两个字节保存的,这样会保存了大量冗余字节0x00。另外一个坏处就是,如果在网络上传输这样的文件,如果由于网络的原因,造成传输中有缺损字节,那么整个utf-16的文档将变成乱码了,因为,你无法知道该文件中的一个字节是Unicode字符的第一个字节还是第二个字节。还有就是utf-16编码方案是与ASCII编码不兼容的。等等这些,都要求我们要有一种更好的编码方案,那就是utf-8。作为Unicode的一种编码方案,它支持的字符集当然是和utf-16是一样的,都是Unicode字符集。utf-8是变字节的编码方案,随着序号的增加,它会从单字节、双字节一直到三字节来表示一个字符,在字符集扩展后,还会出现四字节、五字节的字符。从Unicode字符集序号到utf-8的编码规则如下表显示:

U-00000000 - U-0000007F:

0xxxxxxx

U-00000080 - U-000007FF:

110xxxxx 10xxxxxx

U-00000800 - U-0000FFFF:

1110xxxx 10xxxxxx 10xxxxxx

U-00010000 - U-001FFFFF:

11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

U-00200000 - U-03FFFFFF:

111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

U-04000000 - U-7FFFFFFF:

1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

          例如   Unicode字符U+00A9 = 1010 1001(版权符号)在utf-8 里的编码为:

                    11000010 10101001 = 0xC2 0xA9

                而字符 U+2260 = 0010 0010 0110 0000 (不等于符号) 编码为:

                    11100010 10001001 10100000 = 0xE2 0x89 0xA0

     可以看出utf-8是与ASCII码兼容的,所以它会单字节存储ASCII码字符,而汉字将会用三个字节来存储,这样用utf-8来保存全汉字的文件是效率不高的。另外,可看出按照这种编码,第一个字节前面二进制1的个数就是该字符所用的字节数。当丢失了一段字节时,可以很容易判定下一个字符的起使位置。utf-8的编码标志字节是0xEF BB BF

       在一些老式的系统中,比如一些邮件系统,只支持7bit位字节流,所以也有必要开发一种只支持7位字节的Unicode编码方案,这就是utf-7。它大体的思路是用一个字节0x2B作为一个Unicode字符的开始,然后中间是经过特殊编码的7位的字节序列,当表示完一个字符后,用一个字节0x2d来结束。

       Unicode各种编码方案的编码页是:utf-16CP1200utf-16BECP1201utf-8CP65001utf-7CP65000

       故事到这里就差不多结束了,最后想说的,编码是字符集和编码方案的一个总称,电脑上的一个文件只是某种编码方案下的二进制字节流的一个存储,在没有任何编码方案标志字节的提示下,我们有理由认为它可能是这种编码方案,也可能是别的某种编码方案,当我们用某种指定的编方案打开时,如果所期望的和实际的编码方案不相同但又不互相兼容时,我们看到的就是乱码了。只有找到了正确的编码方案,就可以正确的解读该文件了。当然有经验的人是可以根据乱码的形式来判断其正确的编码方案的。