字符集与字符编码简介(转)

原文链接:http://blog.csdn.net/gogor/archive/2010/02/24/5323599.aspx

 

我们知道,计算机只能识别诸如0101这样的二进制数,于是人们必须以二进制数据与计算机进行交互,或者先将人类使用的字符按一定规则转换为二进制数。

那什么是字符呢?在计算机领域,我们把诸如文字、标点符号、图形符号、数字等统称为字符。而由字符组成的集合则成为字符集,字符集由于包含字符的多少与异同而形成了各种不同的字符集。我们知道,所有字符在计算机中都是以二进制来存储的。那么一个字符究竟由多少个二进制位来表示呢?这就涉及到字符编码的概念了,比如一个字符集有8个字符,那么用3个二进制位就可以完全表示该字符集的所有字符,也即每个字符用3个二进制位进行编码。

我们规定字符编码必须完成如下两件事:

(1) 规定一个字符集中的字符由多少个字节表示;

(2) 制定该字符集的字符编码表,即该字符集中每个字符对应的(二进制)值。

1. ASCII 码:

上个世纪60年代,美国制定了一套字符编码标准,对英语字符与二进制位之间的关系,做了统一规定。这被称为ASCII码,一直沿用至今。

ASCII(American Standard Code for Information Interchange),是一种字符编码标准,它的字符集为英文字符集,它规定字符集中的每个字符均由一个字节表示,指定了字符表编码表,称为ASCII码表。它已被国际标准化组织定义为国际标准,称为ISO646标准。

ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)等。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。这种采用一个字节来编码128个字符的ASCII码称为标准 ASCII 码或者基础ASCII码。

在标准ASCII码表中,0~31及127(共33个)是控制字符或通讯专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等;通讯专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为 8、9、10 和 13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。
32~126(共95个)是可显示字符,其中32是空格,48~57为0到9十个阿拉伯数字;65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
同时还要注意,在标准ASCII中,其最高位(b7)可用作奇偶校验位。所谓奇偶校验,是指在代码传送过程中用来检验是否出现错误的一种方法,一般分奇校验和偶校验两种。奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添1。

但是,由于标准 ASCII 字符集字符数目有限,在实际应用中往往无法满足要求。为此,国际标准化组织又制定了 ISO 2022 标准,它规定了在保持与 ISO646 兼容的前提下将 ASCII 字符集扩充为 8 位代码的统一方法。 ISO 陆续制定了一批适用于不同地区的扩充 ASCII 字符集,每种扩充 ASCII 字符集分别可以扩充 128 个字符,这些扩充字符的编码均为高位为 1 的 8 位代码(即十进制数 128~255 ),称为扩展 ASCII 码。

但是需要注意,各种扩展ASCII码除了编码为0~127的字符外,编码为128~255的字符并不相同。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。

2. ANSI编码标准

标准ASCII码和扩展ASCII码满足了西语国家的需求,但是,随着计算机在世界范围内的普及,对于亚洲国家,如中日韩等国来说,他们使用的符号很多,ASCII字符编码标准远远不能满足其需要,于是这些国家便针对本国的字符集指定了相应的字符编码标准,如GB2312、BIG5、JIS等仅适用于本国字符集的编码标准。这些字符编码标准统称为ANSI编码标准,这些ANSI编码标准有一些共同的特点:

(1) 每种ANSI字符集只规定自己国家或地区使用的语言所需的'字符';比如简体中文编码标准GB-2312的字符集中就不会包含韩国人的文字。

(2) ANSI字符集的空间都比ASCII要大很多,一个字节已经不够,绝大多数ANSI编码标准都使用多个字节来表示一个字符。

(3) ANSI编码标准一般都会兼容ASCII码。

这里要特别提一下我国的几种字符编码标准:GB2312、GBK、GB18030。

字符必须编码后才能被计算机处理。计算机使用的缺省编码方式就是计算机的内码。早期的计算机使用7位的ASCII编码(标准ASCII编码),为了处理汉字,程序员设计了用于简体中文的GB2312和用于繁体中文的big5。
GB2312(1980年)一共收录了7445个字符,包括6763个汉字和682个其它符号。汉字区的内码范围高字节从B0-F7,低字节从A1-FE,占用的码位是72*94=6768。其中有5个空位是D7FA-D7FE。 GB2312支持的汉字太少。1995年的汉字扩展规范GBK1.0收录了21886个符号,它分为汉字区和图形符号区。汉字区包括21003个字符。2000年的GB18030是取代GBK1.0的正式国家标准。该标准收录了27484个汉字,同时还收录了藏文、蒙文、维吾尔文等主要的少数民族文字。现在的PC平台必须支持GB18030,对嵌入式产品暂不作要求。所以手机、MP3一般只支持GB2312。
从ASCII、GB2312、GBK到GB18030,这些编码标准是向下兼容的,即同一个字符在这些标准中总是有相同的编码,后面的标准支持更多的字符。在这些编码中,英文和中文可以统一地处理。区分中文编码的方法是高字节的最高位不为0。按照程序员的称呼,GB2312、GBK到GB18030都属于双字节字符集 (DBCS)。 GB 18030是中国所有非手持/嵌入式计算机系统的强制实施标准。

例如,在Windows中打开记事本,"另存为"对话框的"编码"下拉框中有一项ANSI编码,ANSI是默认的编码方式。对于英文文件是ASCII编码,对于简体中文文件是GB2312编码(只针对Windows简体中文版,如果是繁体中文版会采用Big5码),在日文操作系统下,ANSI 编码代表 JIS 编码,其他语言的系统的情况类似。

3. UnicodeUCSUTF

但是随着互联网的兴起,问题又出现了。由于ANSI码的第一个特点:各个国家或地区在编制自己的ANSI码时并未考虑到其他国家或地区的ANSI码,导致编码空间有重叠,比如:汉字'中'的GB编码是[0xD6,0xD0],这个编码在其他国家的ANSI编码标准中则不一定就是该编码了。于是,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。这样一来当在不同ANSI编码标准之间进行信息交换和显示的时候,乱码就不可避免了。

(1)Unicode

可以想象,如果有一种编码,将世界上所有的符号都纳入其中,每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字所表示的,这是一种所有符号的编码。

Unicode是Universal Multiple-Octet Coded Character Set的缩写,中文含义是"通用多八位编码字符集"。它是由一个名为 Unicode学术学会(Unicode.org)的机构制订的字符编码标准,Unicode目标是将世界上绝大多数国家的文字、符号都编入其字符集,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求,以达到支持现今世界各种不同语言的书面文本的交换、处理及显示的目的,使世界范围人们通过计算机进行信息交换时达到畅通自如而无障碍。由于一个Unicode字符用多个字节表示。这样Unicode编码在不同平台存储时就要注意其字节序了。比如:采用标准Unicode编码的'中'在X86平台上(big endian)的存储就是'2D4E',而在SPARC Solaris上(little endian)的存储则是'4E2D'。

(2) UCS

那什么又是UCS呢,它与Unicode有何关系?

历史上, 有两个独立创立统一字符集的尝试。一个是国际标准化组织(ISO)的 ISO 10646 项目, 另一个是由(一开始大多是美国的)多语言软件制造商组成的协会(unicode.org)组织的 Unicode 项目. 幸运的是, 1991年前后, 两个项目的参与者都认识到, 世界不需要两个不同的统一字符集. 它们合并双方的工作成果, 并为创立一个统一编码表而协同工作。现在,两个项目仍都存在并独立地公布各自的标准, 但 Unicode 协会和 ISO/IEC JTC1/SC2 都同意保持 Unicode 和 ISO 10646 标准的码表兼容, 并紧密地共同调整任何未来的扩展。

国际标准 ISO 10646 定义了通用字符集 (Universal Character Set) UCS。 UCS 是所有其他字符集标准的一个超集. 它保证与其他字符集是双向兼容的. 就是说, 如果你将任何文本字符串翻译到 UCS格式, 然后再翻译回原编码, 你不会丢失任何信息。

ISO 10646 定义了一个 31 位的字符集。 然而, 在这巨大的编码空间中, 迄今为止只分配了前 65534 个码位 (0x0000 到 0xFFFD). 这个 UCS 的 16位子集称为基本多语言面(Basic Multilingual Plane, BMP)。 将被编码在 16 位 BMP 以外的字符都属于非常特殊的字符(比如象形文字), 且只有专家在历史和科学领域里才会用到它们。按当前的计划, 将来也许再也不会有字符被分配到从 0x000000 到 0x10FFFF 这个覆盖了超过 100 万个潜在的未来字符的 21 位的编码空间以外去了。ISO 10646-1 标准第一次发表于 1993 年, 定义了字符集与 BMP 中内容的架构。定义 BMP 以外的字符编码的第二部分 ISO 10646-2 正在准备中, 但也许要过好几年才能完成. 新的字符仍源源不断地加入到 BMP 中, 但已经存在的字符是稳定的且不会再改变了。

UCS 不仅给每个字符分配一个代码, 而且赋予了一个正式的名字. 表示一个 UCS值的十六进制数, 通常在前面加上 "U+", 就象 U+0041 代表字符"拉丁大写字母A"。UCS 字符 U+0000 到 U+007F 与 US-ASCII(ISO 646) 是一致的, U+0000 到 U+00FF 与 ISO 8859-1(Latin-1) 也是一致的。从 U+E000 到 U+F8FF, 已经 BMP 以外的大范围的编码是为私用保留的。

Unicode 字符编码标准与 ISO 10646 的通用字符集(Universal Character Set,UCS)概念相对应,目前的用于实用的 Unicode 版本对应于 UCS-2,即使用16位来表示一个Unicode字符。也就是每个字符占用2个字节。这样理论上一共最多可以表示 65,536(2的16次方) 个字符。基本满足各种语言的使用。实际上目前版本的 Unicode 尚未填充满这16位编码,保留了大量空间作为特殊使用或将来扩展。未来版本会扩充到 ISO 10646-1 实现级别3,即涵盖 UCS-4 的所有字符。UCS-4 是一个更大的尚未填充完全的31位字符集,加上恒为0的首位,共需占据32位,即4字节。理论上最多能表示 2,147,483,648(2的31次方)个字符,完全可以涵盖一切语言所用的符号。

由于Unicode 编码标准与UCS编码标准是相互兼容的,为了方便叙述,下面把二者作为一个统一编码标准来叙述。

(3)UTF

Unicode(UCS)只是一个字符集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字“严”的unicode(UCS)码是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

为了解决这些问题,就出现了UTF。

UTF(Unicode Translation Format),它是Unicode (UCS)的实现(或存储)方式,称为Unicode转换格式。Unicode 的实现方式不同于编码方式。一个字符的 Unicode 编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对 Unicode 编码的实现方式有所不同。

UTF有三种实现方式:

UTF-16:其本身就是标准的Unicode编码方案,又称为UCS-2,它固定使用16 bits(两个字节)来表示一个字符。
UTF-32:又称为UCS-4,它固定使用32 bits(四个字节)来表示一个字符。
UTF-8:最广泛的使用的UTF方案,UTF-8使用可变长度字节来储存Unicode字符,例如ASCII字母继续使用1字节储存,重音文字、希腊字母或西里尔字母等使用2字节来储存,而常用的汉字就要使用3字节。辅助平面字符则使用4字节。UTF-8更便于在使用Unicode的系统与现存的单字节的系统进行数据传输和交换。与前两个方案不同:UTF-8以字节为编码单元,没有字节序的问题。

UTF有三种方案,那么如何在接收数据和存储数据时识别数据采用的是哪个方案呢?

Unicode(UCS)规范中推荐的标记字节顺序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte order Mark。

在UCS编码中有一个叫做"ZERO WIDTH NO-BREAK SPACE"(零宽度非换行空格)的字符,它的编码是FEFF。而FFFE在UCS中是不存在的字符,所以不会出现在实际传输中。UCS规范建议我们在传输字节流前,先传输字符"ZERO WIDTH NO-BREAK SPACE"。这样如果接收者收到FEFF,就表明这个字节流是Big-Endian的;如果收到FFFE,就表明这个字节流是Little-Endian的。因此字符"ZERO WIDTH NO-BREAK SPACE"又被称作BOM。

UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8编码是EF BB BF。所以如果接收者收到以EF BB BF开头的字节流,就知道这是UTF-8编码了。Windows就是使用BOM来标记文本文件的编码方式的。

这样根据识别前面的"ZERO WIDTH NO-BREAK SPACE"字符即可识别编码方案,字节流中前几个字节所表示的编码方式如下:
EF BB BF UTF-8
FE FF UTF-16/UCS-2, little endian
FF FE UTF-16/UCS-2, big endian
FE FF 00 00 UTF-32/UCS-4, little endian.
00 00 FF FE UTF-32/UCS-4, big-endian.

在微软公司Windows XP操作系统附带的记事本中,“另存为”对话框可以选择的四种编码方式除去非 Unicode 编码的 ANSI 外,其余三种“Unicode” 、“Unicode big endian” 和 “UTF-8” 分别对应UTF-16小端(BOM)、UTF-16大端(BOM)和 UTF-8这三种实现方式。

另外,内码是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的。目前Windows的内核已经支持Unicode字符集,这样在内核上可以支持全世界所有的语言文字。但是由于现有的大量程序和文档都采用了某种特定语言的编码,例如GBK,Windows不可能不支持现有的编码,而全部改用Unicode。于是Windows就使用代码页(code page)来适应各个国家和地区不同的字符集。

而所谓代码页(code page)就是针对一种语言文字的字符编码。例如GBK的code page是CP936,BIG5的code page是CP950,GB2312的code page是CP20936。

微软一般将缺省代码页指定的编码说成是内码。缺省代码页指的是:缺省用什么编码来解释字符。例如Windows的记事本打开了一个文本文件,里面的内容是字节流:BA、BA、D7、D6。Windows应该去怎么解释它呢?

是按照Unicode编码解释、还是按照GBK解释、还是按照BIG5解释,还是按照ISO8859-1去解释?如果按GBK去解释,就会得到“汉字”两个字。按照其它编码解释,可能找不到对应的字符,也可能找到错误的字符。所谓“错误”是指与文本作者的本意不符,这时就产生了乱码。

答案是Windows按照当前的缺省代码页去解释文本文件里的字节流。缺省代码页可以通过控制面板的区域选项设置。记事本的另存为中有一项ANSI,其实该项就是指按照缺省代码页的编码方法保存。

1:Unicode编码转换为UTF-8编码的方法

UTF-8就是以8位为单元对Unicode进行编码。下面是Unicode和UTF-8转换的规则:

Unicode UTF-8

0000 - 007F 0xxxxxxx

0080 - 07FF 110xxxxx 10xxxxxx

0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx

例如“汉”字的Unicode编码是6C49。6C49在0800-FFFF之间,所以肯定要用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 110001 001001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

2

当一个软件打开一个文本时,它要做的第一件事是决定这个文本究竟是使用哪种字符集的哪种编码保存的。软件一般采用三种方式来决定文本的字符集和编码:检测文件头标识,提示用户选择,根据一定的规则猜测。

最标准的途径是检测文本最开头的几个字节。

例如,当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码。当你新建一个文本文件时,记事本的编码默认是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,这个字符什么也不是。这就 是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。

其实,如果记事本软件通过检测文件头标识来确定文件的编码方式就可避免该情况,即如果是UTF-8文件,则其文件前三个字节应该是EF BB BF。

 

3

C runtime library中的字符串操作函数(str前缀)是用来处理ASCII的。

Windows提供了处理ANSI字符串的函数,以mbs为前缀,定义在mbstring.h中。

Windows提供了处理Unicode字符串的函数,以wcs为前缀,定义在string.h和wchar.h中。

posted @ 2010-09-14 13:31  泉子  阅读(1919)  评论(0编辑  收藏  举报