字符编码研究

一、ANSI ASCII时期
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编码的字符集。
  ISO/IEC 8859-1 (Latin-1) - 西欧语言
  ISO/IEC 8859-2 (Latin-2) - 中欧语言
  ISO/IEC 8859-3 (Latin-3) - 南欧语言。世界语也可用此字符集显示。
  ISO/IEC 8859-4 (Latin-4) - 北欧语言
  ISO/IEC 8859-5 (Cyrillic) - 斯拉夫语言
  ISO/IEC 8859-6 (Arabic) - 阿拉伯语
  ISO/IEC 8859-7 (Greek) - 希腊语
  ISO/IEC 8859-8 (Hebrew) - 希伯来语(视觉顺序)
  ISO 8859-8-I - 希伯来语(逻辑顺序)
  ISO/IEC 8859-9(Latin-5 或 Turkish)- 它把Latin-1的冰岛语字母换走,加入土耳其语字母。
  ISO/IEC 8859-10(Latin-6 或 Nordic)- 北日耳曼语支,用来代替Latin-4。
  ISO/IEC 8859-11 (Thai) - 泰语,从泰国的 TIS620 标准字集演化而来。
  ISO/IEC 8859-13(Latin-7 或 Baltic Rim)- 波罗的语族
  ISO/IEC 8859-14(Latin-8 或 Celtic)- 凯尔特语族
  ISO/IEC 8859-15 (Latin-9) - 西欧语言,加入Latin-1欠缺的芬兰语字母和大写法语重音字母,以及欧元(€)符号。
  ISO/IEC 8859-16 (Latin-10) - 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。
  (该列表摘于维基百科,http://zh.wikipedia.org/wiki/ISO/IEC_8859)


3、MBCS和ISO/IEC 2022

    以上ASCII或者EASCII都是使用8bit编码的单字节字符集,但是中日韩三国语言使用了数以千计的方块字符,显然使用单个字节不能完全编码,所以这三国使用两个字节来代表一个字符,相应的字符集比如中文的GBK,GB2312,GB18030,BIG5,日文的Shift-JIS等。多字节字符集的国际标准是ISO/IEC 2022,发布于1994年,这应该是参考了中日韩的事实做法后的,在标准化程序上给予承认。
无论是ISO/IEC 8859系列这样的SBCS还是ISO/IEC 2022这样的MBCS,它们都存在重叠的编码空间,所以他们可以独立使用,却不能混合使用。

4、代码页
    这是Windows系统中的一个概念,所谓的代码页其实就是以上所述的各种字符集,比如:
        874  (ANSI/OEM - 泰文) 
        932  (ANSI/OEM - 日文 Shift-JIS)
        936  (ANSI/OEM - 简体中文 GBK/GB2312),GBK不是正式的国家标准,仅是微软与有关部门合作的一个针对GB2312的扩展,所以共用936
        54936 (ANSI/OEM - 简体中文GB18030)
        949  (ANSI/OEM - 韩文)
        950  (ANSI/OEM - 繁体中文 Big5)
        1252 (ANSI - 拉丁文 I)
        1253 (ANSI - 希腊文)
        1255 (ANSI - 希伯来文)
        1256 (ANSI - 阿拉伯文)
    在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编码。如果要写出优雅的跨平台的输入输出代码,存在不小的难度。

posted @ 2011-05-29 17:52  fre2technic  阅读(532)  评论(0编辑  收藏  举报