离线下载
PDF版 ePub版

luoweifu · 更新于 2017-09-20 17:00:34

字符集和字符编码(Charset & Encoding)

原文:字符集和字符编码(Charset & Encoding) 原作者:吴秦(Tyler)

——每个软件开发人员应该无条件掌握的知识!

——Unicode 伟大的创想!

相信大家一定碰到过,打开某个网页,却显示一堆像乱码,如"бЇЯАзЪСЯ"、"�????????"?还记得 HTTP 中的 Accept-Charset、Accept-Encoding、Accept-Language、Content-Encoding、Content-Lang uage 等消息头字段?这些就是接下来我们要探讨的。

目录:

1.基础知识

计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如 'a' 用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致 'a' 解析成 'b' 或者乱码。

字符集(Charset):是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。

字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对。即在符号集合与数字系统之间建立对应关系,它是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息。而以计算机为基础的信息处理系统则是利用元件(硬件)不同状态的组合来存储和处理信息的。元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。

2.常用字符集和字符编码

常见字符集名称: ASCII 字符集、GB2312 字符集、BIG5 字符集、 GB18030 字符集、Unicode 字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

2.1. ASCII 字符集&编码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本 [EASCII]() 则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统(但是有被 Unicode 追上的迹象),并等同于国际标准ISO/IEC 646

ASCII 字符集:主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。

ASCII 编码:将 ASCII 字符集转换为计算机可以接受的数字系统的数的规则。使用 7 位( bits )表示一个字符,共 128 字符;但是 7 位编码的字符集只能支持 128 个字符,为了表示更多的欧洲常用字符对 ASCII 进行了扩展, ASCII 扩展字符集使用 8 位(bits)表示一个字符,共 256 字符。 ASCII 字符集映射到数字编码规则如下图所示:

图 1 ASCII 编码表

图 2 扩展 ASCII 编码表

ASCII 的最大缺点是只能显示 26 个基本拉丁字母、阿拉伯数字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如 naïve 、café、 élite 等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而 EASCII 虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃 ASCII 而转用 Unicode

2.2. GBXXXX 字符集&编码

计算机发明之后的很长一段时间,只用应用于美国及西方一些发达国家,ASCII 能够很好满足用户的需求。但是当天朝也有了计算机之后,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。

天朝专家把那些 127 号之后的奇异符号们(即 EASCII)取消掉,规定:一个小于 127 的字符的意义与原来相同,但两个大于 127 的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE ,这样我们就可以组合出大约 7000 多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在 127 号以下的那些就叫"半角"字符了。

上述编码规则就是GB2312GB2312GB2312-80中国国家标准简体中文字符集,全称《信息交换用汉字编码字符集•基本集》,又称 GB0 ,由中国国家标准总局发布1981 年 5 月 1 日实施。 GB2312 编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持 GB2312。 GB2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75%的使用频率。对于人名古汉语等方面出现的罕用字, GB2312 不能处理,这导致了后来GBKGB 18030汉字字符集的出现。下图是 GB2312 编码的开始部分(由于其非常庞大,只列举开始部分,具体可查看GB2312 简体中文编码表):

图 3 GB2312 编码表的开始部分

由于GB 2312-80只收录 6763 个汉字,有不少汉字,如部分在 GB 2312-80 推出以后才简化的汉字(如"啰"),部分人名用字(如中国前总理朱镕基的"镕"字),台湾及香港使用的繁体字日语朝鲜语汉字等,并未有收录在内。于是厂商微软利用 GB 2312-80 未使用的编码空间,收录 GB 13000.1-93 全部字符制定了 GBK 编码。根据微软资料,GBK 是对 GB2312-80 的扩展,也就是CP936字码表 (Code Page 936)的扩展(之前 CP936 和 GB 2312-80 一模一样),最早实现于Windows 95简体中文版。虽然 GBK 收录GB 13000.1-93的全部字符,但编码方式并不相同。GBK 自身并非国家标准,只是曾由国家技术监督局标准化司、电子工业部科技与质量监督司公布为"技术规范指导性文件"。原始GB13000一直未被业界采用,后续国家标准 GB18030 技术上兼容 GBK 而非 GB13000。

GB 18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集,是 GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与 GB 2312-1980 完全兼容,与GBK基本兼容,支持GB 13000Unicode的全部统一汉字,共收录汉字 70244 个。GB 18030 主要有以下特点:

  • UTF-8相同,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。
  • 编码空间庞大,最多可定义 161 万个字符。
  • 支持中国国内少数民族的文字,不需要动用造字区。
  • 汉字收录范围包含繁体汉字以及日韩汉字

本规格的初版使中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000 年 3 月 17日发布。现行版本为国家质量监督检验总局和中国国家标准化管理委员会于2005 年 11 月 8 日发布,2006 年 5 月 1 日实施。此规格为在中国境内所有软件产品支持的强制规格。

2.3. BIG5 字符集&编码

Big5,又称为大五码五大码,是使用繁体中文(正体中文)社区中最常用的电脑汉字字符集标准,共收录 13,060 个汉字。中文码分为内码及交换码两类, Big5 属中文内码,知名的中文交换码有 CCCII、CNS11643。Big5 虽普及于台湾香港澳门等繁体中文通行区,但长期以来并非当地的国家标准,而只是业界标准倚天中文系统Windows等主要系统的字符集都是以 Big5 为基准,但厂商又各自增加不同的造字与造字区,派生成多种不同版本。2003 年, Big5 被收录到 CNS11643 中文标准交换码的附录当中,取得了较正式的地位。这个最新版本被称为 Big5-2003。

Big5 码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为"高位字节",第二个字节称为"低位字节"。"高位字节"使用了 0x81-0xFE,"低位字节"使用了 0x40-0x7E,及 0xA1-0xFE。在 Big5 的分区中:

Unicode 字符集&UTF 编码

3.伟大的创想 Unicode

——不得不单独说 Unicode

像天朝一样,当计算机传到世界各个国家时,为了适合当地语言和字符,设计和实现类似 GB232/GBK/GB18030/BIG5 的编码方案。这样各搞一套,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。

为了解决这个问题,一个伟大的创想产生了——Unicode 。Unicode 编码系统为表达任意语言的任意字符而设计。它使用 4 字节的数字来表达每个字母、符号,或者表意文字( ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。(并不是所有的数字都用上了,但是总数已经超过了 65535,所以 2 个字节的数字是不够用的。)被几种语言共用的字符通常使用相同的数字来编码,除非存在一个在理的语源学( etymological )理由使之不这样做。不考虑这种情况的话,每个字符对应一个数字,每个数字对应一个字符。即不存在二义性。不再需要记录"模式"了。U+0041 总是代表'A',即使这种语言没有'A'这个字符。

计算机科学领域中, Unicode (统一码、万国码、单一码、标准万国码)是业界的一种标准,它可以使电脑得以体现世界上数十种文字的系统。Unicode 是基于通用字符集( Universal Character Set)的标准来发展而来,并且同时也以书本的形式[1]对外发表。Unicode 还不断在扩增, 每个新版本插入更多新的字符。直至目前为止的第六版,Unicode 就已经包含了超过十万个字符(在2005年,Unicode 的第十万个字符被采纳且认可成为标准之一)、一组可用以作为视觉参考的代码图表、一套编码方法与一组标准字符编码、一套包含了上标字、下标字等字符特性的枚举等。Unicode 组织(The Unicode Consortium)是由一个非营利性的机构所运作,并主导 Unicode 的后续发展,其目标在于:将既有的字符编码方案以 Unicode 编码方案来加以取代,特别是既有的方案在多语言环境下,皆仅有有限的空间以及不兼容的问题。

(可以这样理解:Unicode 是字符集,UTF-32/ UTF-16/ UTF-8 是三种字符编码方案。)

3.1.UCS & UNICODE

通用字符集(Universal Character Set,UCS)是由ISO制定的 ISO 10646(或称 ISO/IEC 10646)标准所定义的标准字符集。历史上存在两个独立的尝试创立单一字符集的组织,即国际标准化组织(ISO)和多语言软件制造商组成的统一码联盟。前者开发的 ISO/IEC 10646 项目,后者开发的统一码项目。因此最初制定了不同的标准。

1991 年前后,两个项目的参与者都认识到,世界不需要两个不兼容的字符集。于是,它们开始合并双方的工作成果,并为创立一个单一编码表而协同工作。从 Unicode 2.0 开始,Unicode 采用了与 ISO 10646-1 相同的字库和字码;ISO 也承诺,ISO 10646 将不会替超出 U+10FFFF 的 UCS-4 编码赋值,以使得两者保持一致。两个项目仍都存在,并独立地公布各自的标准。但统一码联盟和 ISO/IEC JTC1/SC2 都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode 一般都会采用有关字码最常见的字型,但 ISO 10646 一般都尽可能采用Century 字型

3.2.UTF-32

上述使用 4 字节的数字来表达每个字母、符号,或者表意文字 ( ideograph ),每个数字代表唯一的至少在某种语言中使用的符号的编码方案,称为 UTF-32。UTF-32 又称 UCS-4 是一种将Unicode字符编码的协定,对每个字符都使用 4 字节。就空间而言,是非常没有效率的。

这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第 N 个字符,因为第 N 个字符从第 4×Nth 个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它 Unicode 编码使用得广泛。

3.3.UTF-16

尽管有 Unicode 字符非常多,但是实际上大多数人不会用到超过前 65535 个以外的字符。因此,就有了另外一种 Unicode 编码方式,叫做 UTF-16(因为 16 位 = 2 字节)。UTF-16 将 0–65535 范围内的字符编码成 2 个字节,如果真的需要表达那些很少使用的"星芒层(astral plane)"内超过这 65535 范围的 Unicode 字符,则需要使用一些诡异的技巧来实现。UTF-16 编码最明显的优点是它在空间效率上比 UTF-32 高两倍,因为每个字符只需要 2 个字节来存储(除去 65535 范围以外的),而不是 UTF-32 中的 4 个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第 N 个字符,直到它不成立为止这总是一个不错的推断。其编码方法是:

如果字符编码 U 小于 0x10000,也就是十进制的 0 到 65535 之内,则直接使用两字节表示; 如果字符编码 U 大于 0x10000,由于 UNICODE 编码范围最大为 0x10FFFF,从 0x10000 到 0x10FFFF 之间 共有 0xFFFFF 个编码,也就是需要 20 个 bit 就可以标示这些编码。用 U'表示从 0-0xFFFFF 之间的值,将其前 10 bit 作为高位和 16 bit 的数值 0xD800 进行 逻辑 or 操作,将后 10 bit 作为低位和 0xDC00 做 逻辑 or 操作,这样组成的 4 个 byte 就构成了 U 的编码。 对于 UTF-32 和 UTF-16 编码方式还有一些其他不明显的缺点。不同的计算机系统会以不同的顺序保存字节。这意味着字符 U+4E2D 在 UTF-16 编码方式下可能被保存为 4E 2D 或者 2D 4E,这取决于该系统使用的是大尾端 ( big-endian )还是小尾端 ( little-endian ) 。(对于 UTF-32 编码方式,则有更多种可能的字节排列。)只要文档没有离开你的计算机,它还是安全的——同一台电脑上的不同程序使用相同的字节顺序 ( byte order)。但是当我们需要在跨系统之间传输这个文档的时候,也许在万维网中,我们就需要一种方法来指示当前我们的字节是怎样存储的。不然的话,接收文档的计算机就无法知道这两个字节 4E 2D 表达的到底是 U+4E2D 还是 U+2D4E。

为了解决这个问题,多字节的 Unicode 编码方式定义了一个"字节顺序标记( Byte Order Mark)",它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于 UTF-16,字节顺序标记是 U+FEFF。如果收到一个以字节 FF FE 开头的 UTF-16 编码的文档,你就能确定它的字节顺序是单向的( one way)的了;如果它以 FE FF 开头,则可以确定字节顺序反向了。

3.4.UTF-8

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示 Unicode 标准中的任何字符,且其编码中的第一个字节仍与[ASCII]()兼容,这使得原来处理 ASCII 字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持 UTF-8 编码。

UTF-8 使用一至四个字节为每个字符编码:

128 个 US-ASCII 字符只需一个字节编码( Unicode 范围由 U+0000 至 U+007F)。 带有附加符号拉丁文希腊文西里尔字母亚美尼亚语希伯来文阿拉伯文叙利亚文它拿字母则需要二个字节编码(Unicode 范围由 U+0080 至 U+07FF)。 其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。 其他极少使用的 Unicode辅助平面的字符使用四字节编码。 在处理经常会用到的 ASCII 字符方面非常有效。在处理扩展的拉丁字符集方面也不比 UTF-16 差。对于中文字符来说,比 UTF-32 要好。同时,由位操作的天性使然,使用 UTF-8 不再存在字节顺序的问题了,一份以 utf-8 编码的文档在不同的计算机之间是一样的比特流(在这一条上你得相信我,因为我不打算给你展示它的数学原理)。

总体来说,在 Unicode 字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置;组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。所以尽管在 UTF-8 字符串中字符数量与码点数量的关系比 UTF-32 更为复杂,在实际中很少会遇到有不同的情形。

优点

UTF-8 是 ASCII 的一个超集。因为一个纯 ASCII 字符串也是一个合法的 UTF-8 字符串,所以现存的 ASCII 文本不需要转换。为传统的扩展 ASCII 字符集设计的软件通常可以不经修改或很少修改就能与 UTF-8 一起使用。 使用标准的面向字节的排序例程对 UTF-8 排序将产生与基于 Unicode 代码点排序相同的结果。(尽管这只有有限的有用性,因为在任何特定语言或文化下都不太可能有仍可接受的文字排列顺序。) UTF-8 和 UTF-16 都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。 任何面向字节字符串搜索算法都可以用于 UTF-8 的数据(只要输入仅由完整的 UTF-8 字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。

UTF-8 字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的 UTF-8 的可能性很低,并随字符串长度增长而减小。举例说,字符值 C0,C1,F5 至 FF 从来没有出现。为了更好的可靠性,可以使用正则表达式来统计非法过长和替代值(可以查看 W3 FAQ: Multilingual Forms上的验证 UTF-8 字符串的正则表达式)。

缺点

因为每个字符使用不同数量的字节编码,所以寻找串中第 N 个字符是一个 O(N) 复杂度的操作 — 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

4.Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language

在 HTTP 中,与字符集和字符编码相关的消息头是 Accept-Charset/Content-Type,另外主要区分 Accept-Charset/Accept-Encoding/Accept-Language/Content-Type/Content-Encoding/Content-Language:

Accept-Charset :浏览器申明自己接收的字符集,这就是本文前面介绍的各种字符集和字符编码,如 gb2312,utf-8(通常我们说 Charset 包括了相应的字符编码方案);

Accept-Encoding:浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩,支持什么压缩方法(gzip,deflate),(注意:这不是只字符编码);

Accept-Language:浏览器申明自己接收的语言。语言跟字符集的区别:中文是语言,中文有多种字符集,比如 big5,gb2312,gbk 等等;

Content-Type:WEB 服务器告诉浏览器自己响应的对象的类型和字符集。例如:Content-Type: text/html; charset='gb2312'

Content-Encoding:WEB 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应中的对象。例如:Content-Encoding:gzip

Content-Language:WEB 服务器告诉浏览器自己响应的对象的语言。

参考文献&进一步阅读

百度百科. 字符集. http://baike.baidu.com/view/51987.htm, 2010-12-28 维基百科.字符编码. http://zh.wikipedia.org/wiki/%E5%AD%97%E7%AC%A6%E7%BC%96%E7%A0%81, 2011-1-5 维基百科. ASCII. http://zh.wikipedia.org/wiki/ASCII, 2011-4-5 维基百科. GB2312. http://zh.wikipedia.org/wiki/GB_2312, 2011-3-17 维基百科. GB18030. http://zh.wikipedia.org/wiki/GB_18030, 2010-3-10 维基百科. GBK. http://zh.wikipedia.org/wiki/GBK, 2011-3-7 维基百科. Unicode. http://zh.wikipedia.org/wiki/Unicode, 2011-4-30 Laruence. 字符编码详解(基础). http://www.laruence.com/2009/08/22/1059.html, 2009-8-22 Jan Hunt. Character Sets and Encoding for Web Designers - UCS/UNICODE.http://www.uninetnews.com/other_standards/charset.php