编码全解:从ASCII/ISO-8859/GB2312/GBK到Unicode的UCS-2/UCS-4/UTF-8/UTF-16/UTF-32

1、ASCII编码

为了能在电报、打印机、计算机等电信设备上进行信息交换,就必须为不同的设备制定统一的编码格式。早期的电信设备字符编码基本都是使用6位编码。1963年美国国家标准协会(ANSI)制定并公布的ASCII编码是第一个被广泛采用7位编码。

ASCII全称:American Standard Code for Information Interchange,美国信息交换标准码。直至1986年最后一次修订,ASCII共定义了128个字符:

ASCII编码速查表

1.1、控制字符

其中0x00~0x1F这32个属于控制字符,这些控制字符用于控制通信设备,比如0x07(BEL,bell)表示蜂鸣器响铃,0x08(BS,backspace)表示退格,0x0A(LF,line feed)表示换行,0x0D(carriage return)表示回车,0x0C(FF,form feed)表示换页…

回车和换行被分为两个字符是有一定的历史原因的。早期的打字机回车、换行是两个动作。回车就是打字机把纸张重置到行首位置,换行就是把纸张上移一行。
老式打字机

这些控制字符在打印设备与字符屏幕等终端上无法显示,一些终端设备为这些控制字符提供了拓展,使这些字符显示笑脸、扑克牌花色等符号。

1.2、打印字符

0x20~0x7F为可显示字符,其中比较特殊的0x20第32号字符为空格字符,0x7F第127号字符为DEL控制字符。

在很多编程语言中会把空格与控制字符作为空白字符处理。所以实际可显示图形字符共94个。

94这个特殊的数字对于在后面GB2312中会再次出现。

控制字符的拓展

1.3、ASCII编码的国际化

美国的很多国家标准都被ISO组织给国际化了,ASCII也不例外。

国际标准化组织和国际电工委员会ISO/IEC于1972年制订了ISO/IEC 646标准,它来自于多个国家标准(主要是ASCII),允许其他国家根据需要修改ASCII中的$ @ [ \ ] ^ { | } ~12个字符为自己国家使用。

2、EASCII编码

欧洲国家与美国走得最近的,美国在计算机方面的发展也影响到了欧洲国家,编码问题就是其中之一。

很多欧洲国家觉得ASCII码中的95个可显示字符并不能表示本国语言的所有字符。而ASCII码使用7位编码,第8位作为校验纠错位,对于计算机内存而言,校验纠错变得不是那么必要了,使用8位编排256个字符明显不需要额外的编程成本和存储成本。于是许多的制造商在ASCII的基础上添加了128个扩展字符,其中比较流行的是IBM和微软共同制定并在MS-DOS上使用的CP437编码(Code Page 437)。

扩展ASCII编码

3、ISO-8859编码

由于各国制造厂商的EASCII扩展编码不统一,于是ISO/IEC委员会颁布了8位字符编码标准——ISO/IEC 8859

这个编码标准共有16部分,分别以ISO/IEC 8859-n表示(其中ISO/IEC 8859-12印度梵文已废弃),这些字符集可以以ISO-8859-n作为MIME名称,比如最常见的iso-8895-1是西欧语言的8位编码标准。

下面是ISO-8895的16个编码标准:

部分名称颁布时间/最后修订时间
ISO/IEC 8859-1拉丁语系1-西欧1987/1998
ISO/IEC 8859-2拉丁语系2-中欧1987/1999
ISO/IEC 8859-3拉丁语系3-南欧1988/1999
ISO/IEC 8859-4拉丁语系4-北欧1987/1998
ISO/IEC 8859-5斯拉夫语(Cyrillic)1988/1999
ISO/IEC 8859-6阿拉伯语(Arabic)1987/1999
ISO/IEC 8859-7希腊语(Greek)1987/2003
ISO/IEC 8859-8希伯来语(Hebrew)1988/1999
ISO/IEC 8859-9拉丁语系5-土耳其语1989/1999
ISO/IEC 8859-10拉丁语系6-北日耳曼语1992/1998
ISO/IEC 8859-11泰语(Thai)2001
ISO/IEC 8859-13拉丁语系7-波罗的语1998
ISO/IEC 8859-14拉丁语系8-塞尔特语1998
ISO/IEC 8859-15拉丁语系9-西欧1999
ISO/IEC 8859-16拉丁语系10-东南欧2001
ISO/IEC 8859-12原预留给印度梵文,后搁置废弃搁置废弃

4、汉字编码GB2312

信息技术飞速发展,计算机也开始进入中国,但是有个很严峻的问题:中华文化博大精深,光常用汉字就有几千个,这明显无法用单个字节进行编码,于是智慧的中国人民使用两个字节搞出了GB2312编码,并美其名曰:DBCS(Double Byte Charecter Set 双字节字符集)。

4.1、GB2312介绍

GB2312,全称信息交换用汉字编码字符集,代号:GB 2312—1980,GB是“国标”汉字拼音的首字母,1980说明是1980年发布的。GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆,它所收录的汉字已经覆盖中国大陆99.75%的使用频率;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。台湾、香港、澳门等使用繁体字的地区则使用台湾公司开发的BIG5编码。

4.2、GB2312是如何编码的

前面提到ASCII码中0x00~0x20是控制字符的范围,0x20~0x7F是打印字符的范围,而且0x20是空格符,0x7F是DEL控制字符,所以实际可显示字符去掉一个头一个尾剩下0x21~0x7E共94个字符。

ASCII分区

GB2312双字节编码与之对应:0x80~0x9F是预留的控制字符区,0xA0~0xFF0xA00xFF不可用,实际可编码区为0xA1~0xFE,共94个可编码区。两个字节也就是94×94。

”0xA0~0xFF不可用“是为了遵守当时的ISO-2022国际标准。

GB2312编码区

GB2312编码空间为94 x 94,即有94个区,每个区有94个位,这些区分别是记录了那些字符呢:

1、01-09区收录除汉字外的682个字符。

如汉字标点、汉字编号、日文平假名片假名、拼音符号、阿拉伯数字、英文字母、希腊字母。

注意:这里的英文字母、阿拉伯数字、希腊字母和ASCII中的字符不是同一个东西,这些字符占用两个字节,也就是我们常说的全角字符,而ASCII码中的字母数字只占用一个字节所以叫半角字符。

2、10-15区为空白区,没有使用,留作扩展。

3、16-55区收录3755个一级汉字,也就是最常使用的汉字,按拼音排序,从“啊”到“座”。

4、56-87区收录3008个二级汉字,使用频率相对较少的汉字,按部首/笔画排序,从“亍”到“齄”。

5、88-94区为空白区,没有使用,留作扩展。

区号第一个字节范围第二个字节范围:A1~FE实际字符数
01-09A1~A9部分位置未编码682
10-15AA~AF空白0
16~55B0~D7部分位置未编码3755
56~87D8~F7部分位置未编码3088
88~94F8~FE空白0

画成图表示:

GB2312编码分布

第一区从A1A1开始编码,第一个字符是全角空格符。

GB2312第一区

下图是GB2312的16区中的汉字编码,第一个汉字是“啊”。

GB2312的16区

点击这里查看GB2312的编码表

5、对GB2312的扩展—GBK

GB2312已经覆盖了99.75%的使用频率,但是很多人名地名无法用GB2312表示,比如前总理的“镕”字,歌手陶喆的“喆”字,我老家这边有个地名叫“喆桥”。没有这些字上户口办身份证都成问题了,于是国家把GB2312进行了扩展,所以就有了GBK编码。K就是“扩展”的拼音首字母。

5.1、GBK介绍

GBK全称汉字内码扩展规范,是1995年制定的。

GBK编码范围为:8140-FEFE,剔除xx7F码位,共23940个码位。

共收录汉字和图形符号21886个,其中汉字(包括部首和构件)21003个,图形符号883个。

GBK编码支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。

虽然GBK收录GB 13000.1-93的全部字符,但GBK是一种编码方式并向下兼容GB2312;而GB 13000.1-93等同于Unicode 1.1是一种字符集,它的几种编码方式如UTF8、UTF16LE等,与GBK完全不兼容。

GBK是对GB2312-80的扩展,最早实现于Windows 95简体中文版。

GB13000是一个等同于Unicode1.1的字符集,而非编码实现,更多相关内容看下面的统一编码字符集

5.2、GBK编码方式

区位第一个字节第二个字节编码数实际字符数
GBK/1A1~A9A1~FE846717
GBK/2B0~F7A1~FE67686763
GBK/381~A040~FE(7F除外)60806080
GBK/4AA~FE40~A0(7F除外)81608160
GBK/5A8~A940~A0(7F除外)192166
用户定义区AA~AFA1~FE564
用户定义区F8~FEA1~FE658
用户定义区A1~A740~A0(7F除外)672
合计23,94021,886
  • GBK/1区实际上就是原来GB2312的01~09区,并添加了度量单位符号,以及一些特殊图形
  • GBK/2区合并了原来GB2312的一级汉字和二级汉字,也就是原来的1655,5687区
  • GBK/3是扩增的新区,第一个字节为81A0,第二个字节可以是40FE(除去7F),包括大量繁体字
  • GBK/4是扩增的新区,第一个字节为AAFE,第二个字节可以是40A0(除去7F),包括大量繁体字
  • GBK/5是对原来的GB2312的符号区的扩展。
  • 三个用户定义区,包括原来GB2312的两个空白区,再加一个A1~A7的空白区。

GBK编码分布

点击这里查看GBK编码表

事实上最新的中文编码国家标准是GB18030,里面收录了7万多个汉字符号,但由于Unicode的普及,该编码方式已经很少被采用

5.3、验证中文编码

我们在Windows下的记事本中写下啊 亍。“啊”是GB2312第一个中文字符,中间全角空格是GB2312是第一个编码字符,“亍”是二级汉字中的第一个字符。用GB2312保存,理论上应该会得到B0A1 A1A1 D8A1

保存为GB2312编码

选择ANSI保存中文。ANSI代表当前国家或地区编码实现,不同国家的ANSI编码之间互不兼容。

这里的以ANSI保存中文就是以GB2312编码保存

以16进制编辑器打开这个文本:

GB2312十六进制编码

6、统一编码字符集

6.1、ISO 10646标准与Unicode联盟

因为当时每个国家都有自己的一套编码规范,比如大陆有GB2312,台湾有BIG5,欧洲各国有它们相应的ISO-8859编码,日本有JIS,总之这个国家的文件到另外一个国家的电脑上打开就一大堆乱码,跨国公司可能要崩溃了。

1984年,ISO组织决定把各国编码统一起来,造一个“万国码”通行世界,1990年第一份ISO 10646标准就出炉了,但我们中国看到这个标准就很不开心了,因为韩国、日本这些中国周边的国家历来被中华文化熏陶,它们的很多历史文献、日常用字中也包含汉字,所以也有一套自己的中文编码,ISO 10646第一版将韩国、日本等国的汉字编码原封不动的加入到了这个标准,这就导致16位的编码中,已经无法加入中国的现有汉字编码了,只能使用32位表示,最重要的是对于中国、日本、韩国使用的同一个字符,ISO为他设计三个不一样的编码,这明显不利于各国字符编码的统一,还是会出现前面说的乱码问题。

而另一方面,1987年,施乐公司的Joe Becker和苹果公司的Lee Collins开发了统合处理全世界所有文字的统一码。1989年发表了统一码概要,基本为16位。于是中、日、韩文字(CJK)统合了。基本方针为以16位处理所有文字。 1990年,完成了基于此方针的最终草案。隔年1991年1月,大致同意此方案的企业成立了统一码联盟,这里面就包括中国的华为公司。

1991年,各国希望能以一致的方式处理文字,如统一码这般,因而否决了ISO/IEC 10646的初版草案。基于中国与统一码联盟的提议,ISO协会妥协了,最终和统一码联盟成立了中日韩联合研究小组。中日韩联合研究小组将基于各国的汉字编码,独自定义定规范、制作ISO 10646和统一码的统一汉字编码。年尾,完成了Unified Repertoire and Ordering(URO)。1992年,URO就加入了ISO 10646第二版标准中。

点击这里查看Unicode官方介绍

点击这里查看Unicode早年历史

点击这里查看Unicode编码表

6.2、UCS-2 与 UCS-4

Unicode 最初制定的基本方针是以16位处理所有的文字,早期这种编码就称为Unicode编码,由于后来Unicode又出现了UTF系列编码方案,为了区分就把16位定长编码称为UCS-2。对于原来的ASCII码字符,保持其编码值不变,只需将长度扩充为两个字节,而其他国家语言的字符则统一起来并重新进行编码,其中我们中国汉字占的地方最大,当然这里面也包括了日本韩国使用的汉字。

可以点击这里查看Unicode中文字符编码表。

这篇文章讲了Unicode中汉字的相关编码问题

但是2个字节最多能编码65536个字符,而中文简体和繁体加起来就有六万多字(包括后面扩展进去的扩展区),这让其他国家怎么活,于是Unicode还准备了4字节定长编码方案——UCS-4。UCS-4使用32位编码,理论上能编码40多亿的字符,这个肯定能满足全球所有语言的文字了,估计整个银河系统一都用不完。

6.3、Unicode平面

Unicode中每个字符对应的编码值被叫做Code Point(翻译成“码点”或“码位”)。

另外还有一个概念叫做Plane(平面),一个平面由65536()个连续的码点组成,比如0x0000~0xFFFF被称为基本多语言平面(BMP),这个平面编码的字符基本满足日常需求,而UCS-2也只能表示BMP中的字符。

下图便是BMP平面中字符分布图:

BMP字符分布

中间粉红色以及紫红色区域都是我们的汉字

除了BMP外还有很多其他平面,共17个平面,所以码点最大可达到0x10FFFF。

平面始末字符值中文名称英文名称
0号平面U+0000 ~ U+FFFF基本多文种平面Basic Multilingual Plane,简称BMP
1号平面U+10000 ~ U+1FFFF多文种补充平面Supplementary Multilingual Plane,简称SMP
2号平面U+20000 ~ U+2FFFF表意文字补充平面Supplementary Ideographic Plane,简称SIP
3号平面U+30000 ~ U+3FFFF表意文字第三平面(未正式使用)Tertiary Ideographic Plane,简称TIP
4号平面至13号平面U+40000 ~ U+DFFFF(尚未使用)
14号平面U+E0000 ~ U+EFFFF特别用途补充平面Supplementary Special-purpose Plane,简称SSP
15号平面U+F0000 ~ U+FFFFF保留作为私人使用区(A区)Private Use Area-A,简称PUA-A
16号平面U+100000 ~ U+10FFFF保留作为私人使用区(B区)Private Use Area-B,简称PUA-B

第一辅助平面SMP
第二辅助平面SIP

7、UTF编码体系

随着计算机一起发展起来的还有互联网,那时候网络传输价格还是蛮高的,如果使用UCS-2或UCS-4对文本数据进行网络传输,那将消耗很大的带宽,特别是对于使用ASCII码或ISO-8895的美国和欧洲国家,原本可以用1个字节表示的字符,非要用两个字节甚至四个字节,这宽带费蹭蹭地往上涨啊。于是ISO组织和Unicode联盟共同开发了面向传输的Unicode编码方案——UTF(Unicode Transformation Format)。这个编码体系有UTF-1、UTF-7、UTF-8、UTF-16、UTF-32。其中被广泛接受的是UTF-8和UTF-16以及UTF-32。

7.1、UTF-8

UTF-8是一种变长Unicode编码方式:

字节数代码点的位数最小码点最大码点字节1字节2字节3字节4
17U + 0000U + 007F0XXXXXXX
211U + 0080U + 07FF110XXXXX10XXXXXX
316U + 0800U + FFFF1110XXXX10XXXXXX10XXXXXX
421U + 10000U + 10FFFF11110XXX10XXXXXX10XXXXXX10XXXXXX
  • 对于任意字节,如果第一位为0,则该字节表示单独的一个ASCII字符
  • 对于任意字节,如果第一位为1,第二位为0,则该字节是多字节字符中的某个字节
  • 对于任意字节,如果前两位为1,第三位为0,则该字节是两个字节表示的字符的第一个字节。
  • 对于任意字节,如果前三位为1,第四位为0,则该字节是三个字节表示的字符的第一个字节。
  • 对于任意字节,如果前四位为1,第五位为0,则该字节是四个字节表示的字符的第一个字节。

总结起来就是:一个字符的第一个字节前有多少个连续的1,这个字符就需要多少个字节表示,特殊的第一位为0,单独一个字节为一个ASCII字符。

很显然这种编码方式完全兼容ASCII码,而这种编码方式唯一受益的就是使用ASCII码的国家(→_→),他们几乎不用修改任何内容就能让他们的网站被各国浏览,欧洲国家还是得用两个字节来编码,汉字一般需要三个字节(孤僻汉字可能要用到四个字节)。所以对于一些欧洲国家,他们的网站如果没有国际化的要求仍可以使用ISO-8895编码方案;对于中国网站,不需要国际化也仍可以使用GBK。

7.2、UTF-16

7.2.1、UTF-16代理区

在BMP中有一个区域:U+D800~U+DFFF,被称为UTF-16代理区(UTF-16 Surrogates)。

因为Unicode字符集的编码值范围为0-0x10FFFF(17个平面),而大于0xFFFF的平面区码点值无法用2个字节来表示,所以Unicode标准规定:BMP中U+D800~U+DFFF范围内的值不对应于任何字符,为代理区。

这个代理区在下面讲述UTF-16的编码方式的时候会有很大的作用。

前面讲Unicode平面的时候给了一张BMP分布图,可以参照这张图来理解代理区的概念。

7.2.2、UTF-16的编码方式

1. 对于BMP平面的字符(也就是小于等于0xFFFF的码点,因为UTF-16代理区不对应任何字符,所以这里不包含代理区):
编码方式与UCS-2完全相同,直接使用字符码点值编码。
2. 对于其他平面的字符(也就是大于0xFFFF的码点):

  • 将码点值减去0x10000,得到的值的范围为0~0xFFFFF,也就是可以用20位表示的值。(因为Unicode分为17个平面,最大码点值为0x10FFFF,减去一个0x10000,最大值为0xFFFFF),这个20位的值写成二进制:HHHHHHHHHHLLLLLLLLLL。
  • 将相减得到的20位的高10位加上0xD800得到一个16位的码元,这个由高10位得到的码元称为高位代理(high surrogate)**,它的范围为:0xD800~0xDBFF(0xD800加上一个10比特的数)。由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)**。
  • 将相减得到的20位的低10位加上0xDC00得到另一个16位的码元,这个由低10位得到的码元称为低位代理(low surrogate)**,它的范围为:0xDC00~0xDFFF(0xDC00加上一个10比特的数)。由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)**。
  • 由其他平面的码点值最终的到的UTF-16编码的二进制表示为:110110HHHHHHHHHH 110111LLLLLLLLLL

由UTF-16的编码方式可以看出UTF-16编码完全兼容UCS-2编码的,可以说UCS-2是UTF-16编码方式的一个子集。这也是设计UTF-16的目的,因为Unicode刚推出来时,大部分软件都是使用UCS-2,比如微软的Windows操作系统专门为Unicode重新设计了一份API,刚开始就是使用UCS-2;Java的Character和String等字符处理类最初都是使用UCS-2定长编码。

而且前导代理(0xD8000xDBFF)、后尾代理(0xDC000xDFFF)、BMP有效码元(0x00000xD7FF和0xE0000xFFFF),三者互不重叠,意味着任意位置的16位码元都可以确定边界,也就是说UTF-16和UTF-8一样是自同步的,可以通过检查码元可以判定给定字符的下一个字符的起始码元。

7.3 UTF-32

相对于UTF-8和UTF-16而言,UTF-32使用场合的就比较少了,因为BMP平面的字符基本能满足日常用字的需要,所以使用UTF-32存储无疑是巨大的空间浪费。在存储方式上UTF-32与UCS-4一样使用4个字节存储数据,所以UTF-32和UCS-4一样也是定长编码,区别在于UTF-32的前导比特(前面的11位)必须为0,只能表示个Unicode字符,在形式上UTF-32是UCS-4的子集。

7.3.1、小端序与大端序

由于UTF-16是以16位作为码元,也就是两个字节作为一个单位,这就涉及到一个问题:高位字节在前,还是低位字节在前?

先来了解一下两个概念:小端存储和大端存储。

  • 由于CPU对数据进行运算是以字节为单位,将数据从内存读取到CPU的算术逻辑单元(ALU)中:先读低位字节,后读高位字节。所以数据按照低字节存放在低地址,高字节存放在高地址的原则存储在内存中的,CPU从低地址向高地址读取数据的时候就会先读取到低位字节的数据。这就是所谓的“小端存储”(little-endian,缩写为LE),为了方便记忆也可以叫做“低尾端(低字节在后的意思)”。

    小端存储

  • 显然小端存储不符合人类的直观读取顺序,所以对于文件存储以及网络传输这种与CPU接触较少的情况一般都是使用大端存储(big-endian,缩写为BE)**:高字节存放在低地址,低字节存放在高地址**。为了方便记忆也可以叫做“高尾端(高位字节在后的意思)”。

    大端序

7.3.2、BOM字节序标记

UTF-16由两个字节组成,在存储或运算的时候有大端序、小端序的分别。但是计算机如何识别UTF-16的文本是大端序还是小端序呢。于是在UTF-16文本文件前面加两个字节的标记来标识该文件是小端序还是大端序,这个标记叫BOM(Byte Order Mark,字节序标记)。这两个字节的标记为FE FF则表示是大端序(高位在后);如果标记为FF FE则表示是小端序(低位在后)。

相应地,UTF-32也有四个字节的BOM标记,四个字节值为00 00 FE FF表示是大端序,四个字节值为FF FE 00 00表示小端序。

UTF-8虽然以单字节作为码元,但也可以有BOM标记EF BB BF,UTF-8的BOM不是用来标记字节序的,而仅仅用作标识UTF-8文件。

7.4、验证Unicode编码

Unicode中第一个汉字为“一”,码点值为U+4E00。

  1. 我们用记事本写下“一”。

记事本

  1. 选择保存格式

    保存格式

  2. 保存为UTF-8后,以二进制方式打开

    UTF-8

    EF BB BF为UTF-8文件的BOM标记,E4 B8 80的二进制表示方式为11100100 10111000 10000000。去掉UTF-8的标识信息就剩下:00100 111000 000000,这个二进制对应十六进制为4E00。

  3. 保存为Unicode,也就是UTF-16LE小端存储方式,然后以二进制方式打开

    UTF-16LE

    FF FE标识该文件是UTF-16小端存储。00 4E对应4E00

  4. 保存为Unicode big endian,也就是UTF-16BE大端存储方式,然后以二进制方式打开

    UTF-16BE

    FE FF标识该文件是UTF-16大端存储。4E00就是“一”的码点值。

从这里也可以看出,中文以UTF-16方式存储所占空间比UTF-8所占空间更小。

参考文献

维基百科:相关链接在文章中已经给出

各种编码详解:https://my.oschina.net/liting/blog/470021

本作品采用 知识共享署名 4.0 国际许可协议 进行许可。

转载时请注明原文链接:https://blog.hufeifei.cn/2017/06/ComputerAndOS/%E4%BB%8EASCII%E5%88%B0Unicode/

鼓励一下
支付宝微信