2014-12-11
在计算机内部,所有的信息最终都表示为一个二进制的字符串。每一个二进制位(bit)有0和1两种状态,因此一个字节(八个二进制位)就可以组合出256种状态,每一个状态对应一个符号,就是256个符号,从00000000到11111111。
ASCII是American Standard Code for Information Interchange的缩写,ASCII编码第一次以规范标准的型态发表是在1967年,最后一次更新则是在1986年。 ASCII码一共规定了128个字符的编码,包括95个可显示字符(32-126)和33个不可显示字符(0-31,127),可见字符包括0-9,A-Z,a-z,英式标点符号等字符。这128个符号只占用了一个字节的后面7位,最前面的1位统一规定为0。
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示,其他使用拉丁字母的语言(主要是欧洲国家的语言),都有一定数量的附加符号字母,于是,国际标准化组织(ISO)及国际电工委员会(IEC)联合制定了一系列8位字符集的标准,0-127用来兼容ASCII编码,128-255用来定义这些国家的附加字母,不同国家的附加符号是不一样的,因此共定义了15个单字节字符集,其中最常见的是 ISO-8859-1,即西欧语言。
这15字符集编号为1-11,13-16,没有12,所有这15个字符集都兼容ASCII编码,所以英语可以使用任意一个来表示。
在ISO-8859-x字符集里,128-255部分代表的字母是不一样的。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字。
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么电子邮件常常出现乱码?就是因为发信人和收信人使用的编码方式不一样。
可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字所表示的,这是一种所有符号的编码,Unicode为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2014年6月16日公布的7.0.0。
目前的Unicode字符分为17组平面(Plane),包括基本多文种平面(Basic Multilingual Plane,简写BMP,它又简称为“零号平面”, Plane 0)和16个辅助平面,每平面拥有65536(即2个字节)个代码点。BMP和辅助平面合计需要占据21位的编码空间,比3字节略少,但事实上辅助平面字符仍然占用4字节编码空间,与UCS-4保持一致。
基本平面编码用U+hhhh表示,包含了常见的绝大多数字符,包括英文字符,西欧字符,常见汉字等等,基本满足各种语言的使用。U+0000-U+00FF完全兼容ISO-8859-1编码。
Plane 1摆放拼音文字(主要为现时已不再使用的古老文字)、手写文字和音符等符号,用于学者的专业论文中使用这些古老或过时的语言书写符号。Plane 2放置的都是一些罕用的汉字或地区的方言用字,主要为中日韩统一表意文字扩展区。
Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)
例如,如果一个仅包含基本7位ASCII字符的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。
UTF-8是使用最广的一种Unicode的实现方式,是一种变长的编码方式,它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。UTF-8的编码规则很简单,只有二条:
下表总结了UTF-8编码规则,字母x表示可用编码的位。
对于ASCII字符,在UTF-8里对应单字节,对于Unicode Plane 0里的其他字符,可能对应UTF-8的2个字节或3个字节,中文是3字节。对于以英文为主的文档,使用UTF-8编码可以有效节省空间,对于中文为主的文档,可能会增加50%空间。
UTF-16的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。
对于Unicode基本平面的字符,使用2个字节,UTF-16码与Unicode码是一样的。
对于辅助平面,使用4个字节,在基本平面里,从U+D800到U+DFFF是空段,16个辅助平面共需2^4 x 2^16 = 2^20,即20位表示,UTF-16将这20位拆成两半,前10位映射在U+D800到U+DBFF,称为高位(H),后10位映射在U+DC00到U+DFFF,称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。
UTF-16编码导致ASCII码会增大1倍,但好处是基本平面的字符和UTF-16是一一对应的,在文件传输和存储时一般不用UTF-16编码,但很多程序内部会使用UTF-16编码,这样字符串的查找和操作效率最高,譬如Java,.Net等较新的语言,都使用Unicode为内部编码。
UCS-2又是什么编码?曾经有两个团队,不约而同想搞统一字符集。一个是1988年成立的Unicode团队,另一个是1989年成立的UCS团队。等到他们发现了对方的存在,很快就达成一致:世界上不需要两套统一字符集。1991年10月,两个团队决定合并字符集。也就是说,从今以后只发布一套字符集,就是Unicode,并且修订此前发布的字符集,UCS的码点将与Unicode完全一致。
UCS的开发进度快于Unicode,1990年就公布了第一套编码方法UCS-2,使用2个字节表示已经有码点的字符。UTF-16编码迟至1996年7月才公布,明确宣布是UCS-2的超集,即基本平面字符沿用UCS-2编码,辅助平面字符定义了4个字节的表示方法。
所以,UCS-2是2字节编码,对应了Unicode基本平面。
UTF-32是一种最直接的也是最笨的编码方法,使用固定的4个字节表示一个Unicode字符,UTF-32字节内容一一对应Unicode编码,因此转换最简单,缺点是浪费空间,ASCII字符会增大4倍,所以实际上没有人使用这种编码方法。与UTF-32对应的是UCS-4编码。
Unicode码可以采用UCS-2格式直接存储。以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
因此,第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。 那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。 如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
在Windows上打开"记事本"程序Notepad.exe,新建一个文本文件,内容就是一个"严"字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。 然后,用文本编辑软件UltraEdit中的"十六进制功能",观察该文件的内部编码方式。
下面是几个中文编码: