【字符编码】你真的了解字符编码吗

腾讯大讲堂——字符编码的前世今生
字符串,那些你不知道的事
编码字符集标准及分类研究
通信用語の基礎知識 —— ISO/IEC 2022
ISO 2022 介紹(1): 標準

1. 说明

1.1 字符编码贡献的相关的组织

  • ANSI:美国国家标准学会,发布了ASCII编码。是ISO/IEC中的一员,曾和ECMA联合制定过ECMA-94。

  • ECMA:ECMA是欧洲标准组织,当时ASCII只适合英国美国使用,因为他们的拉丁字母没有变音字母,所以128个字符够用。但是欧洲许多国家并不够用,所以ECMA制定了一些相关标准。

  • ISO/IEC:分别是国际标准化组织International Organization for Standardization和国际电工委员会International Electrotechnical Commission。国际化标准组织也意识到统一字符编码是他们的工作,所以联合IEC开始制定标准,并且将一些现有的标准纳入国际标准,例如ASCII对应ISO/IEC 646, ECMA-35对应ISO/IEC 2022。ECMA-94对应ISO/IEC 8859-1 ~ ISO/IEC 8859-4。

  • 统一码联盟:The Unicode Consortium,是一个统筹统一码(Unicode)发展的非营利机构,其宗旨为最终以统一码取代现存的字符编码,因为现存编码不能够在多语言电脑环境中使用,而且字符数有局限。同时它也制定了数种统一码转换格式(UTF,Unicode Transformation Format)。统一码的成功让电脑使用进入了一个新纪元,并应用于很多新技术,如XML、Java编程语言和现今的操作系统。

1.2 相关术语说明

由于历史遗留问题,字符集、字符编码、编码表这三个术语不用区分的太清楚,一般都是在说同一个东西。
而码位就很明确了,就是说字符集中有多少个码位,例如ASCII有128个码位,而每个码位又对应一个编码(或者说编号也行),例如a的码位是97,a的编码是97。

重点说下字符编码,编码的意思并不是说编成计算机中的二进制,而是赋予字符一个码位,还是例如a是97……
而97在计算机中,使用7位,还是一个字节,还是两个字节就不知道了。
不过要说编码的意思就是编程二进制也不完全错,所以这几个属于概念的界限是很模糊的。如

  • 等同于:文章中可能出现等同于这个说法,例如GB13000等同于Unicode1.1,指的是等同采用,另外还有修改采用,和非等效采用这里就不多说了。可以看百度百科——国家标准代码

2. 字符编码的发展

下面基本是按照发展顺序排列,但并不全是。想知道大概发展时间的可以看 腾讯大讲堂——字符编码的前世今生

2.1 ASCII

American Standard Code for Information Interchange 美国信息转换标准代码

ASCII第一次以规范标准的类型发表是在1967年(最早公布于19)。它是一种7bit字符集,一共能表示128种符号。
而ASCII编码设计于早期,它的作用不仅只是显示字符,还包含一些控制字符,用于文本控制,操作终端等,例如7(0000 01110x07)是代表响铃,10代表换行键。
控制字符一共有33个,分别是0~31和127(或者说0x00 ~ 0x1F和0x7F,为了放便,后面均使用十进制表示)。其余的则是英文标点、数学运算符、阿拉伯数字和大小写拉丁字母(英文字母)。
控制字符是不可显示的,但是有些终端已经提供了扩展,使得这些字符可显示为诸如笑脸、扑克牌花式等8-bit符号,且这33个字符多数都已是陈废的控制字符。

2.2 EASCII

Extended ASCII

ASCII的缺点是只能显示基本的拉丁字母,阿拉伯数字和一些英式标点符号,因此只能用于显示现代美国英语。所以当计算机流行到欧洲国家时,就有了各种各样的扩展ASCII,统称EASCII。它将ASCII扩充到一个字节,8bit, 一共能表示256个字符,向下兼容ASCII。但是很明显,各种EASCII是互不兼容的。
著名的EASCII是IBM的代码页437,是早期IBM-PC使用的编码系统。

2.3 ISO/IEC 646

ISO/IEC 代表两个组织,分别是国际标准化组织International Organization for Standardization和国际电工委员会International Electrotechnical Commission。
ISO和IEC什么时候联合成小组的就不知道了,上面引用的文章中说是1984年是错误的,ISO/IEC 646公布于1972年,肯定是早于这个年份才对。

ISO/IEC在1972年公布了ISO/IEC 646标准,它是一个7bit字符集,来自数个国家国家标准,主要来自美国的ASCII。
可以说,ASCII等同于ISO/IEC 646。等同于的意思就是大部分相同,少量修改或扩展。

而ISO/IEC 646和ASCII主要区别在于:

  • ISO/IEC 646将ASCII码中的# $ @ [ \ ] ^ ` { | } ~这12个码位去除,留给其它国家自己定义。
  • 而且ISO/IEC 646规定,可以将一些标点符号附加在拉丁字母后面,并且跟上一个退格符键符(backspace,码位为0x8)),用于表示变音符号,例如á可以用a/表示,而编码中a/后面会跟着一个退格键符,用于终端识别这是个变音符号。

至此,各国可以根据ISO/IEC 646产生自己的变体7bit字符集。ASCII也是符合这个标准的,因为ISO/IEC 646本身就是参考自ASCII。

2.4 ISO/IEC 2022

ISO 2022可以说是字符编码的里程碑,用了很巧妙的方式统一了混乱的编码问题,并且提供了多字节编码的扩充标准。我们熟知的ISO 8859-1,GB2312都是使用这套标准。
这套标准非常复杂,下面的解释可能不太准确,不过大概就是这个意思。

1963年公布的ASCII码是第一个得到广泛采用的7位字符编码,这时的通信领域的协议采用了第8位做校验纠错用途。但是对计算机内存来说,纠错位不是必要的,因此8位字符编码逐渐出现,用来表示比ASCII码更多的字符。
而为了8位字符编码也能使用7比特信道传输,ISO 2022规定了7位字符集和8位字符集的采用相同的结构,使得它们可以互相转换。并且,ISO 2022还规定了多字节字符编码的扩展方式,使单字节和多字节、7位还和8位,都能使用同一套编码解析。而GB2312、ISO 8859-1都是是同这套标准。

ISO 2022标准是怎么兼容7比特信道的呢?
它可以将任意字节长度的字符集,都转换成任意数量的7bit编码表。


对于7位字符编码,0/0 到 1/15(column/row)是控制字符,一共32个,表示CL(control left)区域。
2/0 到 7/15 是图形字符,一共94个(为了兼容ASCII码表,左上角和右下角被sp空格符和del符占用的码位留空),表示GL(graphic left)区域。

对于8位字符编码,在兼容ASCII的同时,还使用到了CR和GR区域,如下图,大家可以找下ISO 8859-1的编码表对照下,ISO 8859-1也是使用ISO 2022标准的一个字符编码系统。

  • CL(0/0〜1/15)
  • GL(2/0〜7/15)
  • CR(8/0〜9/15)
  • GR(10/0〜15/15)

控制字符是CL和CR区域,图形字符是GL和GR区域。
CR和CL位置对应,CR的码位减去160即CL的码位,GR和GL同理,但是GR包含94或者96个字符,左上角和右下角的空位可以被填充。

当8位字符编码兼容7bit传输信道时,即:将GR区域偏移到GL区域,不过左上角和右下角的字符可能会丢失。

而多字节字符编码系统,采用的是区位表示法,并且是一种变长编码,即可以使用两个字节表示一个字符,也可以使用一个字节表示ASCII字符。
例如双字节字符编码,最多可以表示94x94或96x96个字符。高位字节用来表示94个区,低位字节用来表示每个区中的94个码位,码位都是和ASCII码表的码位对应的,是为了使用统一的方法解析编码。
而三字节编码,就是可以表示94x94x94个字符了,不过实际上并没有符合ISO 2022标准的三字节编码表。

当需要解析某个字节时,会使用到缓冲区,一共有6个缓冲区,C0 C1 G0 G1 G2 G3,用于动态分配不同的字符集,每个缓冲区用于存储不同类型的字符集。C0 C1对应控制字符,G0对应94个字符的编码表,G1对应94x94类型的字符编码表,G2对应96个字符的编码表,G3对应96x96类型的字符编码表。如图


例如用GB2312编码解析字节时,将GB2312字符集指派(designate)到G1缓冲区,然后调用到GR区域,通过该字节对应的码位找到对应字符。
再比如有两个字节,10110000 10100001,十六进制为B0A1,转换成区位码(GB2312使用GR区域,减去160则是区位码)则是第16页第1个字符 “啊”。首先GB2312字符集指派到G1缓冲区,然后通过某种方式(locking shift)将第16页调用到GR区域,然后找到GR区域的第一个字符是“啊”,显示到屏幕。

而这个过程如何指派,如何调用是通过转移序列(escape sequence)控制,转义序列是以ESC字符开头的一系列字节。这个说起来有些复杂,本身ISO 2022并不是重点,这里已经讲太多了,有兴趣的可以看引用的几篇文章,推荐ISO 2022 介紹(1): 標準

2.5 ISO/IEC 8859-1

在ISO/IEC 2022发布后,ISO/IEC也根据这个标准制定了一系列的名为ISO/IEC 8859-x的单字节字符集,分别从ISO 8859-1 到 ISO 8859 -16等十几个编码系统,提供给各个地区使用。其中由于未知原因,ISO 8859-12并没有使用。
其中ISO/IEC 8859-1的流通性最广,顾而这里使用ISO/IEC 8859-1作为标题。

2.6 GB 2312

GB是国标的拼音首字母。

在ISO/IEC 2022发布后,我国也根据这个标准制定了GB 2312标准,全称《信息交换用汉字编码字符集·基本集》。于1980年发布,1981年5月1日正式实施。
GB2312是一种可变长的双字节编码系统(上面就说了ISO/IEC 2022S是可变长的),可以用一个字节表示ASCII的字符,用两个字节表示中文或其它字符。

2.7 BIG5

GB2312只支持简体字符,所以GB2312发布后不久,台湾地区制定了BIG5标准,包括13,053个字与441个符号,供港澳台地区使用。
BIG5并不是按照ISO 2022标准制定的,采用双字节编码方式。

2.8 ISO/IEC 10646 /UCS

ISO 2022是字符编码领域设计最为精巧复杂的编码机制 ,极大地促进了各种国家 /地区编码字符集标准的发展 ,提供了字符集的标识和切换机制以实现多语言环境下的编码扩充 ,使得不同语言文字语境下的信息交换成为可能。以 Unix为代表的开放系统在实现中广泛应用了众多 ISO 2022的派生方案 ,曾一度代表了系统软件国际化的前沿水平。尽管 ISO 2022的主要目标是要服务于多语言环境 ,但在实施过程中暴露出了一些难以解决的问题 ,包括 :实现开销可观 ,字符集的选择依赖于环境 ,需要上层能识别相应字符集等。实践说明以多个独立编码字符集为基础进行动态切换的多语言支持方案无法达到预期目标 ,问题的关键是字符编码只有在一致语境中才能正确解释 ,包容世界上所有文字的单一编码字符集才是合理的方案 ,通用字符集 UCS(Universal Character Set)因此应运而生。

ISO/IEC 10646标准定义了31位的UCS通用字符集(Universal Coded Character Set),中文译作“通用多八位编码字符集”。

ISO/IEC 10646规定了四个八位位组 (分别表示组、平面、行、字位 )构成的编码空间。一共128个组,每个组256个平面,每个平面256行,每行256个码位,全部编码空间的大小为 2^31。
实际上目前只用到了第0组中的17个平面,其中 0平面中码位被分配给了各种文字的最常用字符 ,称为基本多文种平面 BMP,而其它平面则称为辅助平面。BMP的使用覆盖率可达97%

而UCS有两种编码方式,分别是UCS-2和UCS-4,UCS-2使用16位编码空间,而UCS-4使用32位编码空间,也就是一个字符占用两个字节或4个字节。
而对于一个常用平面(BMP),UCS-2就足够了,但还想表示其它平面的字符,就需要用到UCS-4了。

2.9 Unicode

位于美国加州的Unicode组织允许任何愿意支付会费的公司和个人加入,其成员包含了主要的电脑软硬件厂商,例如奥多比系统、苹果公司、惠普、IBM、微软、施乐等。
20世纪80年代末,组成Unicode组织的商业机构,和国际合作的国际标准化组织因为电脑普及和信息国际化的前提下,分别各自成立了Unicode组织和ISO-10646工作小组。他们不久便发现对方的存在,大家为着相同的目的而工作。
1991年,Unicode 1.0发布,包含7,161个字符,但是不包含CJK(中日韩统一表意文字)字符。
1992年,Unicode 1.0.1发布,包含CJK最初的20,902个字.
1993年,Unicode 1.1发布,对应ISO/IEC 10646的最初版本。
之后的每一个Unicode版本都对应着相同的ISO/IEC 10646版本,而Unicode1.0和1.0.1的制定有没有ISO参与就不清楚了。

Unicode在字符、字符命名、码位分配方面和ISO 10646(UCS)是保证一致的,因此两个标准在编码字符集层次上完全等同,只是 Unicode在此基础上增加了重要的实现算法、字符属性以及其他语义信息方面的规定。

当然Unicode的编码方式和UCS是一样的,但编码方式和实现方式不同,编码只是赋予字符一个编号,而实现方式则是确确实实的将字符转成计算机可识别的二进制,或者说定义了Unicode字符在内存中怎么存储。这些实现方式叫UTF(Unicode Transformation Format,Unicode 转换格式)。当然,我们也可以直接叫UTF编码,文章开头就说了不要太在意这个术语,下面就用UTF编码称呼。

UTF有几种方式实现:

  • UTF - 32: UTF-32是UTF-*家族中唯一的一个定长编码,使用4个字节编码,和UCS_4是等价的。它的好处很明显,不需要什么特殊的算法就可以直接用4个字节表示UCS字符集中的字符,二进制和USC字符编码的相同。但是劣势更明显,7位就可以表示的英文字母也消耗4个字节的编码空间。
  • UTF - 16: UTF-16是一种变长编码,最少可以用两个字节来表示一个字符,只使用两个字节的时候,和UCS_2是等价的,足够用于表示第0平面的字符。而对于辅助平面,会通过某种算法,延长到4个字节来表示辅助平面的字符。

UTF-16 与 UTF-32 还有一个不明显的缺点。我们知道不同的计算机存储字节的顺序是不一样的,这也就意味着U+4E2D 在 UTF-16 可以保存为4E 2D,也可以保存成2D 4E,这取决于计算机是采用大端模式还是小端模式,UTF-32 的情况也类似。为了解决这个问题,引入了 BOM (Byte Order Mark),它是一特殊的不可见字符,位于文件的起始位置,标示该文件的字节序。对于 UTF-16 来说,BOM 为U+FEFF(FF 比 FE 大 1),如果 UTF-16 编码的文件以FF FE开始,那么就意味着其字节序为小端模式,如果以FE FF开始,那么就是大端模式
其他 UTF-* 编码的 BOM 可以参考 Representations of byte order marks by encoding

  • UTF - 8:UTF-8可以说是我们比较熟悉的一种Unicode编码实现方式了,它和UTF - 16一样,是变长编码。不过它最小可以使用一个字节来表示一个ASCII字符。对于扩展的ASCII,例如ISO 8859-1,会使用到两个字节,对于BMP(基本多文种平面)则使用到三个字节,而辅助平面的字符则会用到四个字节。
    UTF-8的实现算法基于以下几个特点:
    • 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
    • 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
    • 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
    • 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
    • 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;

因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。如图:



通过这种用最高位bit判断的方式,UTF-8 就不存在字节顺序在大小端不同的情况,也就是说不需要BOM的(即文件开头有没有 U+FEFF)。不过由于微软会在UTF-8的编码中也加上BOM,为了明确区分ASCII编码和UTF-8编码,但这样的文件在其它系统中可能会出现问题,所以很多编辑器中可以看到UTF-8分有BOM和无BOM两种。
而UTF-8的优势非常明显,对于存储ASCII字符非常节省内存,但是如果表示BMP平面的字符,就需要多用到一个字节,例如中文在UTF-16只要两个字节,在UTF-8需要三个字节。

还有UTF-1,UTF-7等不太常用这里不作说明。
而我国还有个GB18030,完全兼容GB2312,基本兼容GBK,剩余的码位可以通过偏移得到Unicode的码位,也可以算是一种UTF(Unicode的变换格式)。

2.10 GB 13000

《信息技术 通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》

等同于ISO/IEC 10646:1993,说白了,GB13000实际上就是UCS的BMP平面,其实也就是Unicode1.1。

2.11 GBK

另外再说下我们熟知的GBK,它是对GB2312的扩充,K就是“扩”的拼音首字母,它兼容GB2312,但是并不是使用ISO 2022标准的。
它实际上是微软的CP936编码表,最早出现在windows95中。将GB13000的全部字符填充到GB2312的空码位上。虽然它包含GB13000的全部字符,但它并不兼容Unicode。

3. 总结

说那么多可能有点乱,这里总结下常用的一些字符编码。

  • ASCII:美国信息交换标准代码,一共包含128个字符,其中33个控制字符,33个英文标点,10个阿拉伯数字和52个大小写拉丁字母。用7位编码空间即可表示,占用一个字节,第8位在早期用来纠错,可以百度“校验位”。

  • ISO-8859-1:使用最多的ISO 2022标准的字符集,使用8位编码空间,占用一个字节。

  • GB2312:我国发表的符合ISO 2022标准的字符集,使用到区位码的方式编码,一共94个区,每个区94个码位,为了兼容ASCII码表,码位均分布在GR区(160~255)。一共包含汉字6763个和非汉字图形字符682个。部分区位没有使用

  • GBK:微软扩展的GB2312,收录了GB13000的所有字符(或者说unicode1.1)。填充到GB2312空白的区位上,兼容GB2312,但是不兼容Unicode。
    GBK的K代表中文的“扩”,GBK并不属于国家标准,而是一个“技术规范指导性文件”,它的前身其实是微软windows95上的cp936编码。

  • UCS:ISO 10646定义的字符集,拥有31位编码空间,4个字节分别代表126个组,每组256个平面,每个平面256页,每页256个字符。和Unicode同步发展,目前定义了17个平面,第0平面称为基本多文种平面(BMP),包含所有的常用字符,其余的16个平面称为辅助平面。
    而UCS的编码分为UCS_2和UCS_4两种,UCS_2使用两个字节,只能编码基本多文种平面的字符。而UCS_4使用四个字节,可以编码所有平面。

  • Unicode:和UCS同步发展,和UCS使用相同的字符集,但是额外定义了编码的实现方式,称为UTF(Unicode转换格式, Unicode Transformation Format),而常用的UTF有UTF-32,UTF-16,UTF-8。

  • UTF-32:和UCS_4编码一样,采用4字节编码,是一种定长编码方式,即ASCII的7位编码空间的字符也一律使用4字节编码。另外,由于计算机系统分为大端模式和小端模式,二进制的排列方式相反,不同顺序编码会得到不同字符。所以需要用一个特殊的不可见字符在文件开头来标记顺序,U+FFEF表示大端模式,U+FEFF表示小端模式。而这两个字不可见字符称为BOM(Byte Order Mark)。

  • UTF-16:和UCS_2编码一样,采用2字节编码,单它是一种变长的编码方式,如果要表示辅助平面的字符,可以变长到4个字节变编码,和UTF-32一样,需要BOM。

  • UTF-8:一种变长编码方式,用开始字节的最高位来判断字符的占用字节,具体规则看上文。最低可以用1个字节来表示ASCII编码字符,两个字节表示EASCII的编码字符(例如ISO 8859-1),3个字节表示基本多文种平面的字符,4个字节表示其它辅助平面的字符。
    UTF-8是不需要BOM的,通过高位字节即可确定编码顺序,但是因为历史遗留原因,Windos系统一般也给UTF-8加上BOM标识以便明确区分Unicode和其它代码页,所以一些编辑器上可以看到UTF-8分为有BOM和无BOM两种。

4. 猝

上次写完闭包的文章之后,决定不再纠结这种问题,结果还是陷进来了,几分钟能简单了解的知识点硬生生扩充到使用几天来写一篇文章……
不过确实懂了好多东西=。=,另外鄙视以下百度,基本搜不出你想要的东西,推荐科学上网。

推荐阅读更多精彩内容

  • 想来想去终于决定写一个关于计算机字符编码的笔记了( 应该说终于愿意去学了..(๑•ᴗ•๑)), 原本的目的只是搞清...
    Justin13阅读 943评论 1 11
  • 原文在这里:各种字符集和编码详解 在软件的编码和实现中,我们可能会碰到个 一个比较头疼的问题--编码,不同字符间的...
    舌尖上的大胖阅读 997评论 0 2
  • 字符是用户可以读写的最小单位。计算机所能支持的字符组成的集合,就叫做字符集。字符集通常以二维表的形式存在。二维表的...
    刘惜有阅读 4,080评论 1 12
  • 什么是字符集,什么是字符编码,它做什么用? 字符(Charcter)是文字与符号的总称,包括文字、图形符号、数学符...
    laravel阅读 125评论 0 0
  • 我想我再也不会回到那里,因为那里没有你。 我炒了一盘洋葱,泪流满面。 好想回到小时候,回到有你的日子,不再长...
    单小白阅读 29评论 0 2