字符编码研究
1、ASCII
美国信息交换标准码(American Standard Code for Information Interchange)开始于50年代后期,最后完成于1967年。这是一个7bit编码的字符集(开发过程中放弃了6bit和8bit编码的方案),含有128个字符。后来ISO根据ASCII,在1972年制定了ISO/IEC 646标准,同样是7bit编码,供各国使用。
2、EACSII(Extended ASCII)
ASCII字符集由于自身的优势,很快流行起来。但是当处理美国之外的国家的语言时,遇到了问题,比如西欧的一些国家需要使用重音符号,货币符号也不是$,俄罗斯等一些国家根本不使用拉丁字母,而是使用斯拉夫字母。幸运的是,此时由于成本的下降,8bit编码已经可以接受(事实上,IBM在大型机上已经使用多年),国际标准化组织在ASCII的基础上,制定了一系列的8bit编码的字符集。
(该列表摘于维基百科,http://zh.wikipedia.org/wiki/ISO/IEC_8859)
3、MBCS和ISO/IEC 2022
无论是ISO/IEC 8859系列这样的SBCS还是ISO/IEC 2022这样的MBCS,它们都存在重叠的编码空间,所以他们可以独立使用,却不能混合使用。
4、代码页
这是Windows系统中的一个概念,所谓的代码页其实就是以上所述的各种字符集,比如:
在windows的区域设置中可以设置代码页。
以上字符集,习惯上称为ANSI字符集。
二、UCS与Unicode时期
ANSI系列字符集在处理各自国家的语言时,可以工作得很好,但不支持混合使用多国语言。而且,当与使用不同语言的电脑交换文档时,如果选择了错误的代码页,那么将出现乱码现象。为了解决这个问题,人们意识到需要一个统一的字符集。
1、ISO/IEC 10646
从1984年开始,ISO开始制定一套能包含世界所有文字和符号的统一编码方案,此项目的结果是ISO/IEC 10646标准,字符集称为UCS(Universal Multiple-Octet Coded Character Set)。该标准在80年代后期发布了草案,编码结构有以下特点:
1)编码空间是31bit,使用4个8bit组来表示
2)编码兼容ISO 2022,避开C0和C1两个控制码区
第二个特点,导致了编码空间的严重浪费,以16bit元为例,最大编码空间有65535,但是只能使用35532,仅达到最大编码空间的54%。
2、Unicode
当ISO/IEC 10646的草案公布后,美国的一些电脑制造商(软件和硬件)强烈反对(这或许是编码空间确实浪费得太严重了)。他们提出新的编码方案:
1)字符集的基本构造块,由7bit或者8bit提升到16bit,这样,编码空间有65535
2)采用连续编码,不刻意避开C0和C1两个控制区,以增大实际编码空间
这个方案称为Unicode。
这个世界需要两个统一编码方案吗,答案是不需要。所以合并应该可以说是必然的。经过不断的斗争(政治的或者经济的),双方达成妥协,决定合并双方的工作成果:
1)统一编码方案采用Unicode方案,即放弃对ISO 2022的兼容,采用连续编码,以充分利用编码空间
2)16bit的Unicode编码方案并入ISO 10646,作为第0平面,即BMP平面
3)ISO负责后续字符的收集和整理,但是字符的编码分配不能大于0x10FFFF(原因后面解释)
由于Unicode和ISO/IEC 10646除了编码还各自包含了一些其他规定,比如Unicode包含了一些语言的排序、为印刷而存在的多种文字混合处理的算法等,所以两个项目组在保证编码兼容的前提下,仍然各自发布自己的标准。
3、UCS-2和UCS-4编码方案
ISO 10646最开始针对31bit的编码空间定义了两种编码方案,UCS-2和UCS-4,分别用两个8bit组和四个8bit组来表示。UCS-2仅能表示BMP平面的字符,而UCS-4可以表示所有字符。
4、UTF-16和UTF-32
由于Unicode方案的基本构造单元是16bit组,当表示BMP平面的字符时,没有任何问题,但是如果需要表示BMP平面以外的字符,将不敷使用。Unicode协会给出的解决办法就是UTF-16。具体的编码方式是:
1)当表示BMP平面的字符时,使用一个16bit组
2)当表示BMP平面以外的字符时,使用两个连续的16bit组,称为代理(surrogates)。
当使用代理时,具体的做法是,前一个16bit组(称为高半字符),限定为0xD800~0xDBFF,后一个16bit组(称为低半字符)限定为0xDC00~0xDFFF。那么两个16bit组合在一起,一共可以有(0xDBFF - 0xD800 - 1) x (0xDFFF - 0xDC00 - 1) = 0x10FFFF,大概100万个编码空间,一共16个UCS平面。这也是为啥ISO 10646标准承诺不分配大于0x10FFFF编码的原因。
UTF-32与UCS-4采用相同的构造方式,他们的区别是UCS-4中,大于0x10FFFF的值是合法的(由于ISO和Unicode项目组的约定,实际并不存在),但是在UTF-32中,大于0x10FFFF的值是不合法的。
当前,由于大部分常用字符都包含在BMP平面中,所以使用UCS-4或者UTF-32的成本和收益不成比例,所以绝大多数需要处理统一字符集的时候,都是使用UCS-2和UTF-16,由于UCS-2的天生缺陷(不能表示BMP之外的字符),所以UTF-16应该说是较优的选择(BMP之外的字符极不常用,但是谁也不能保证肯定不用)。
5、Unix-like与UTF-8
现实并不总是如此美好,Unix世界中,由于很多工具都是围绕ASCII打造的,要迁移到UTF-16,将付出不小的代价。所以UTF-8应运而生,这种编码方式有以下特点:
1)基本构造块仍然使用8bit元
2)针对ISO 10646的31bit编码空间,使用不定长的1~6个8bit组来表示字符,当表示ASCII字符时,仅用一个字节,与ASCII编码相同。
3)不需要指定字节序
UTF-8解决了Unix世界的问题,而且由于不需要指定字节序,而且编码模式中存在易于被程序识别的模式,所以这是目前兼容性最好的信息交换编码方式。
6、Windows与UTF-16
UTF-8是兼容性最好的信息交换方式,但是由于不定长的构造方式,在程序处理上是不利的。windows平台没有Unix-like平台的遗留问题,所以windows内部使用的是UTF-16(从windows2000开始,之前的NT仅支持到UCS-2,非NT内核不支持unicode,仅支持ANSI系列字符集),无论用户输入的是什么字符集,windows内部都存储为UTF-16,当调用windows api时,如果传入的参数不是UTF-16的,那么将被自动转换为UTF-16。
在不需要做细致区别的情况下,以下使用unicode来代表统一字符集。
三、编程环境对Unicode的支持
讨论两种典型的环境,一种是C#(已经大规模使用的语言中,最新的),另一种是C/C++(已经大规模使用的语言中,存在时间很长的)
1、C#
C#使用char类型(映射到System.Char)来表示基本构造块,这是一个16bit组,内部编码是UTF-16(即可以处理BMP平面之外的字符,当然,此时单个char没有意义)。C#没有提供8bit组构造块。
2、C/C++
char是唯一可以确定的,这是一个8bit组,但是wchar_t却依赖于编译器,在gcc中,是32bit,而在ms c中是16bit。在linux上,最好使用char,然后使用UTF-8编码。在windows平台,使用微软提供的若干宏定义,可以比较容易的处理UTF-16编码。如果要写出优雅的跨平台的输入输出代码,存在不小的难度。