很久很久以前,西方兴起了一个著名的组织——巫师协会,他们维护者世界的和平,为世界人民提供各种先进的机器和魔法。
后来,在十九世界四五十年代,在巫师协会中,产生了一群年轻的巫师,他们思想先进,精力充沛,他们试图去尝试发明一种可以让机器思考的机器。
开始的时候,他们使用了8个可以开合的电子管来表示不同的状态,他们惊喜的发现,这是多么的有趣和富有实际意义的事情,于是,他们给它取了个名字“字节”。
再后来,为了和“字节”沟通,他们使用了更多的电子管(大约1W多个)来组成了一个巨大的机器,他们发现这台机器工作的非常好,于是,就给它起了个名字“计算机”。
由于这些年轻的巫师诞生的地方是在一个叫做“米国”的国家,所以,计算机开始也只在米国使用。这些巫师们发现8位的字节可以组合出2的8次方(256)种不同的状态。为了避免混乱,他们制定了一个规定:把编号从0开始的32种状态限定为特殊的用途,让它们和终端,打印机进行交流。当终端和打印机遇到这些字节的时候,就需要做一些它们之间早已经约定好的动作。如果遇到00x07,则终端就向他们嘟嘟的叫,如果需要00x10,终端就换行,如果絮叨0x1b,打印机就会打印反白的字,或者终端使用彩色显示字母。巫师们觉得这样做非常好,于是就把这些0x20以下的字节状态称为“控制码”。
为了把他们自己的语言描述出来,他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。巫师们又觉得这样做非常好,于是就给它起了个名字“ANSI的‘ASCII’编码”( American Standard Code for Information Interchange,美国信息互换标准代码)。由于巫师协会的影响力,当时世界上所有的计算机都用同样的ASCII方案来表示英文文字。
后来,随着魔法的普及,世界上很多国家都开始使用计算机,然而,悲剧的是,很多国家使用的语言并不是英文,他们的一些语言文字并不能用ASCII来表示。于是,这些国家的巫师一致决定使用127号之后的空位来表示自己的新出现的,在ASCII里面没有的字母和符号,他们还加入了很多横线,竖线,交叉线等符号,一直把编号用到了最后一个状态255。这些巫师们把128到255页的字符集成为“扩展字符集”。
再后来,古老的东方文明终于开始学习西方的技术,他们也开始使用计算机了。但让他们郁闷的是,现在已经没有更多的可利用字节状态来表示他们的语言文字了,而且,这个古老的国度有着数万的汉字需要表示。但这并没有给聪明的中国人民造成麻烦,他们中的一些具有影响力的大巫师提出了把字节状态中127号后面的那些奇奇怪怪的符号直接去掉,然后制定新的标准:把小于127的字符的意义保留,把大于127的两个字符连接在一起,用来表示一个汉字,前面的一个字节(他们称之为高字节)从0xA1用到0xF7,后面一个字节(他们称之为低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。而且,在这些编码里,他们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,他们称他为"全角"字符,而原来在127号以下的那些称之为"半角"字符。
这些东方的巫师们觉的这样很不错,于是就给这种编码方式起了个名字叫做“GB2312”。他们自豪的说,GB2312是对ASCII的一种中文扩展。但是,中华民族的文化太古老了,以至于他的很多汉字使用GB2312都没法打出来,于是,这些大巫们毫不客气的把GB2312中没有用到的码位全部用上。
后来发现还是不够用,于是巫师们新规定,不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。他们把扩展之后的编码方案被称为GBK标准,GBK包括了GB2312的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。
而后来,一些少数民族也开始用起了电脑,于是大巫们继续扩展,又加了几千个新的少数民族的字,GBK标准扩成了GB18030。从此之后,中华民族的文化就在这些大巫们的努力下,可以在计算机世界中传承了。
后来,一些中国的年轻巫师觉得这一系列的编码标准非常的不错,于是就在巫师之中起了一个通用的名字来给它命名“DBCS”( Double Byte Character Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的魔法语言为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。
在那个时候,我们经常可以看到一写年老的大巫在教一些小巫们咒语:“一个汉字两个英文字符!一个汉字两个英文字符……”。
天下太大了,以至于这些巫师们不能预料的事情太多,因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的DBCS编码方案——当时的中国人想让电脑显示汉字,就必须装上一个"汉字系统",专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持BIG5编码的什么"倚天汉字系统"才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办?
这真是一个让上帝都头疼的问题啊。于是,上帝派大天使加百利下凡拯救这些巫师和普通人民。加百利化身为一个大巫,组建了一个新的巫师协会ISO(国际标谁化组织)!并且邀请世界各地有权威的大巫来一起加入ISO组织,用以解决各种疑难问题。
他们日夜思考,终于提出了一个新的方案:废了现有的地区性编码方案,重新设计出一个括了地球上所有文化、所有字母和符号的编码方式——"Universal Multiple-Octet Coded Character Set",他们称他为“UCS”,而其他巫师干脆直接叫他"UNICODE"。
他们规定:ISO标准必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些“半角”字符,UNICODE包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高8位永远是0,因此这种方案在保存英文文本时会多浪费一倍的空间。
在UNICODE开始流传的年代,一些年老的巫师们发现了一个令他们非常诧异的现象:他们的strlen函数突然一夜之间变得难以捉摸,一个汉字的长度不再是相当于两个字符了,而是一个!于是一些年轻的巫师们又反过来告诉那些他们曾经的老湿,从UNICODE开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的"一个字符"!同时,也都是统一的"两个字节"。(请注意"字符"和"字节"两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号)。而在UNICODE中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。
UNICODE的出现,很快得到了很多民间组织的大力支持,从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE对于他们来说是一个很好的一揽子解决方案,于是从WindowsNT开始,MS趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用UNICODE方式工作的版本,从这时开始,WINDOWS系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。
但是,凡事有好有坏,UNICODE在制订时并没有考虑与任何一种现有的编码方案保持兼容,这使得GBK与UNICODE在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。
然而,巨大的有点显然是显而易见的,UNICODE是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概用到银河联邦成立那一天也估计用不完了吧!
UNICODE来到时,一起到来的还有计算机网络的兴起,UNICODE如何在网络上传输也是一个必须考虑的问题。由于UNICODE编码不兼容ISO8859-1编码(这个编码和ASCII编码一样是单字节编码),而且容易占用更多的空间:因为对于英文字母,UNICODE也需要两个字节来表示。所以UNICODE不便于传输和存储。因此ISO的大巫们有制定了UTF(UCS Transfer Format)编码,UTF编码兼容ISO8859-1编码,同时也可以用来表示所有语言的字符,不过,UTF编码是不定长编码,每一个字符的长度从1-6个字节不等。另外,UTF编码自带简单的校验功能。一般来讲,英文字母都是用一个字节表示,而汉字使用三个字节。
注意,虽然说UTF是为了使用更少的空间而使用的,但那只是相对于UNICODE编码来说,如果已经知道是汉字,则使用GB2312/GBK无疑是最节省的。不过另一方面,值得说明的是,虽然UTF编码对汉字使用3个字节,但即使对于汉字网页,UTF编码也会比UNICODE编码节省,因为网页中包含了很多的英文字符。
顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。
只要受到过网络编程加持的巫师们清楚的明白,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的INTEL架构,而另一些是采用高位先发送的方式,在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位,那就发送"FEFF",反之,则发送"FFFE"。
讲到这里,我们再顺便说说一个很著名的传说:当你在windows的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!这被人传说为联通之所以拼不过移动的原因。实际上这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。
下面引用从网上引来一段从UNICODE到UTF8的转换规则来解释这个问题:
Unicode:0000 – 007F 0080 – 07FF 0800 - FFFF
UTF-8: 0xxxxxxx 110xxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx
例如"汉"字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。
而当你新建一个文本文件时,记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是:
c1 1100 0001
aa 1010 1010
cd 1100 1101
a8 1010 1000
注意到了吗?第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",不好意思,这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。
而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。
很久以前看到几篇关于字符的文章,也是用故事讲述的,原文出处已经找不到了,所以自己也用这样的方法描述了一下自己的理解。
参考资料:
ASCII http://zh.wikipedia.org/zh-cn/ASCII
GB_2312: http://zh.wikipedia.org/zh-cn/GB_2312
GBK : http://zh.wikipedia.org/zh-cn/GBK
Unicode: http://zh.wikipedia.org/zh-cn/Unicode
UTF-8 : http://zh.wikipedia.org/zh-cn/UTF-8