首页 > 代码库 > 编码发展历史

编码发展历史

第一阶段 ASCII

在计算机中,所有的数据只可能是0或者1(用高电平和低电平分别表示1和0),那么我们通常看到的字符也就只能用0和1来表示呀。于是科学家们(这里指的是美国的科学家)就想出一个办法,把一个特定的数字对应一个特定的字母进行存储和传输,比如我需要存储字母a,那么我存入一个数字97(即在计算机中存入二进制(01100001),这个过程叫做编码(encode),而我们在读取数据的时候,当遇到97时,我们就让计算机显示字母a,这个过程叫做解码(decode)。
为了大家在数据传输的时候不至于产生误会,那么我们需要让所有的人都使用数字97来代表字母a,所以需要制定一份标准(即码表),最开始的这个标准叫做ASCII码表。
规则

  • 所有的控制字符(比如CR回车、DEL删除等)编码在0-31范围以及127中。
  • 把所有的标点符号,英文大小写全部放在32-126范围中。
  • 防止以后出现需要补充的情况,把128-255位这么多位置留出来,应该足够用了吧!所以设置一个字节8位二进制,把这个标准叫American Standard Code for Information Interchange(美国标准信息交换代码,简写为ASCII),标准制定结束。

等等,我们的中文字呢?
美国的"砖家"没有考虑那么久远呢!这就是后面出现那么多问题的根源所在啊。
实现方式
第一位始终未0,后面7位表示0-127的范围,一个数字对应一个字母或者标点符号,亦或者控制符号,即所有的ASCII码的统一形式为0xxxx xxxx。

第二阶段 GB2312,GBK, BIG5 Latin1, ISO-8859-1, JIS, ANSI...

计算机技术到了欧洲,欧洲人发现怎么我们的那么多符号没有编进去啊!
所以欧洲"砖家"坐到了一起,开始讨论。
发现既然美国人把第一位流出来了,那么我们就用128-255的位置好了。
规则
128-159之间为控制字符,160-255位文字符号,其中包括了西欧语言、希腊语、泰语、阿拉伯语、希伯来语。
刚好把美国人给的空间全部用完,世界真美好,谢谢美利坚预留的每一个位置。
砖家们决定把他们的编码名称叫做Latin1,后面由于欧洲统一制定ISO标准,所以又有了一个ISO的名称,即ISO-8859-1。
实现方式

  • 0-127的所有位置不动,那么可以兼容ASCII,二进制位0xxx xxxx
  • 128-255位置全部用完,二进制位1xxx xxxx
    由于所有的位置全部用完,而欧元符号实在指定标准之后才出现的,所以在这个码表中连欧洲人自己的货币符号都没有办法放进去。

计算机技术当然也传到了亚洲大地,比如中国。
中国砖家们坐在一起发现,美国人搞的这个东西真的有问题,预留才128-255的空间,可是我们的汉字个数远远超出了这个数目啊,怎么办??
后面聪明的中国砖家们发现,只能使用2个字节了,否则真的搞不定。
由于必须和美国原来制定的ASCII不冲突,所以指定了如下规则
规则

  • 如果一个字节中第一位为0,那么这就是一个ASCII字符。
  • 如果一个字节中第一位为1,那么这个是汉字,认定需要2个字节才表示一个编码的文字。
    把这个码表叫GB2312
    这个码表中包含汉字6763个和非汉字图形字符682个。
    还有很多的空间没有用到,索性全部预留了吧。
    实现方式
  • 0xxxxxxx:表示为ASCII字符
    -1xxxxxxx 1xxxxxxx:表示为汉字

后来,中国砖家们发现,很多的不常用汉字没有在码表中,于是添加了很多的汉字进去,这个编码叫做GBK,实现方式和GB2312是完全一样的,兼容GB2312,当然也兼容ASCII。
实现方式

  • 0xxxxxxx:表示为ASCII字符
    -1xxxxxxx xxxxxxxx:表示为汉字

后面再次添加更多的字符进去,再次命名为GB18030,兼容GBK。由于汉字很多,2个字节并不能完全包括进去,所以GB18030采用2\4位混编的形式。

当然计算机也传到了日本(JIS)、韩国、台湾(BIG5)等等地方,大家全部发挥自己的聪明才智,各自实现了自己的编码。这些编码都与ASCII兼容,但是相互之间不兼容。

使用 2 个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码,又称为"MBCS(Muilti-Bytes Charecter Set,多字节字符集)"。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码,所以在中文 windows下要转码成gb2312,gbk只需要把文本保存为ANSI编码即可。 不同ANSI编码之间互不兼容

第三阶段

随着通讯越来越多,而老美发现在自己公司需要国际化的时候,自己原来埋的这个雷真的害了自己。
于是乎,开始研讨把世界上几乎所有文字全部放在一个码表中,而这个包罗万象的码表就叫做Unicode,即万国码。
Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。
实际上,在软件制造商的协会(unicode.org)在做这个工作时,国际标准化组织(ISO)在做同样的事情,最后大家都意识到世界上并不需要两个不同的万国码,于是大家坐在一起合并研究的成果,最后的结果就是现在的Unicode。

各个编码及其范围

ASCII

编码范围00-7F,其中00-1F、FF为控制字符。其它为英文字母、数字、标点符号。

Latin1

编码范围00-FF,其中00-7F同ASCII,80-9F为控制符、9F-FF为字母和标点符号.

CP1252

微软的企业标准,补充了一些符号和欧元符号,为Latin1的超集。

GB2312

编码范围为A1A1-F7FE(剔除xx7F),共23940个码位。其中很多区间没有用到,而汉字使用的区间为B0A1-F7FE,其他为标点符号和特殊字符。
除常用简体汉字字符外还包括希腊字母、日文平假名及片假名字母、俄语西里尔字母等字符,未收录繁体中文汉字和一些生僻字。
对汉字进行了分区管理,其中第一个字节为区位码,包括下面区位。
01-09区为特殊符号。
16-55区为一级汉字,按拼音排序。
56-87区为二级汉字,按部首/笔画排序。
10-15区及88-94区则未有编码。
第二个字节为位字节,01-94总计94个。
为什么实际选择不是01-5E,而是选择A1-F7的位置呢?
因为英文可见字符区间为20-7F,加上128(也就是最高位为1)后得到的取件即是A1-FE
区位码使用了0xA1-0xF7(把01-87区的区号加上0xA0),位字节使用了0xA1-0xFE(把01-94加上 0xA0)

GBK

编码范围为8140-FEFE,兼容GB2312,仍然有部分区间没有用到。
GBK也支持希腊字母、日文假名字母、俄语字母等字符,但不支持韩语中的表音字符(非汉字字符)。GBK还收录了GB2312不包含的 汉字部首符号、竖排标点符号等字符。

CP936

CP936是微软指定的标准,属于企业标准,和GBK的有些许差别,绝大多数情况下可以把CP936当作GBK的别名。

BIG5

Big5是双字节编码,高字节编码范围是0x81-0xFE,低字节编码范围是0x40-0x7E和0xA1-0xFE。和GBK相比,少了低字节是0x80-0xA0的组合。0x8140-0xA0FE是保留区域,用于用户造字区。
Big5收录的汉字只包括繁体汉字,不包括简体汉字,一些生僻的汉字也没有收录。

CP950

微软的企业标准,可以理解为是对 Big5的扩展。

GB18030

编码范围同GBK,补充了更多的字符,由于Unicode开始流行且GB18030补充的字符都比较生僻,所以实际使用上基本是GBK。
GB18030编码是变长编码,有单字节、双字节和四字节三种方式。GB18030的单字节编码范围是0x00-0x7F,完全等同与ASCII;双字节编码的范围和GBK相同,高字节是0x81-0xFE,低字节的编 码范围是0x40-0x7E和0x80-FE;四字节编码中第一、三字节的编码范围是0x81-0xFE,二、四字节是0x30-0x39。

Unicode

中文的编码范围为4E00-9FCF,其中9FC4-9FCF之间的区间没有使用。
一个蛋疼的问题就是这个区间全部都是文字,中文标点没有包含在其中,中文标点散落在各个位置。详细请看http://blog.chinaunix.net/uid-12348673-id-3335307.html。
一些特殊的文字和中文部首以及一些特殊符号也不在此范围内,详细情况可以参考网址:http://www.cnblogs.com/sosoft/p/3456631.html

编码方面的一些概念

大尾(big endian)和小尾(little endian)

大尾和小尾是CPU处理多字节数的不同方式。例如“汉”字的Unicode编码是6C49。那么写到文件里时,究竟是将6C写在前面,还是将49写在前面?如果将6C写在前面,就是big endian。还是将49写在前面,就是little endian。

BOM

UTF-8以字节为编码单元,没有字节序的问题。UTF-16以两个字节为编码单元,在解释一个UTF-16文本前,首先要弄清楚每个编码单元的字节序。例如收到一个“奎”的Unicode编码是594E,“乙”的Unicode编码是4E59。如果我们收到UTF-16字节流“594E”,那么这是“奎”还是“乙”?
Unicode规范中推荐的标记字节顺序的方法是BOM,即Byte Order Mark。BOM是一个有点小聪明的想法:
在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编码了。

Unicode的实现方式

Unicode只是进行了编码,也就是说只是一个码表,至于具体怎么实现,并没有规定。
下面是Unicode的几种实现方法。

UTF-8

范围字节数存储格式
0x0000~0x007F (0 ~ 127)1字节0xxxxxxx
0x0080~0x07FF(128 ~ 2047)2字节110xxxxx 10xxxxxx
0x0800~FFFF(2048 ~ 65535)3字节1110xxxx 10xxxxxx 10xxxxxx
0x10000~1FFFFFF(65536 ~ 2097152)4字节11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0x2000000~0x3FFFFFF5字节111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0x4000000~0x7FFFFFFF)6字节1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

理论上说,UTF-8并没有大小尾的困扰,所以并不需要BOM。
但是一些Windows应用会指定大小尾,比如Notepad,而且Excel在没有指定BOM的UTF-8文件进行读取时会使用Windows的codepage,从而出现错误。

UTF-16

在Unicode基本多文种平面定义的字符(无论是拉丁字母、汉字或其他文字或符号),一律使用2字节储存。而在辅助平面定义的字符,会以代理对(surrogate pair)的形式,以两个2字节的值来储存。
UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节 (2字节) 储存,但UTF-16却无法兼容于ASCII编码。
可以认为UTF-16是下面介绍的UCS-2的父集。在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。现在若有软件声称自己支援UCS-2编码,那其实是暗指它不能支援在UTF-16中超过2bytes的字集。对于小于0x10000的UCS码,UTF-16编码就等于UCS码。
如果一个UTF-16文件没有指定BOM,默认应该是UTF-16BE,但是在Intel x86中却是UTF-16LE。所以在现实世界中有很多的没有指定大小尾的UTF-16却是UTF-16LE。

UTF-32

每一个Unicode码位使用恰好32位元。可以粗暴的认为UTF-32和下面要介绍的UCS-4是等同的。

UCS-2

采用2个字节,定长的表示每一个字符,所以总计可以表示2^16个字符。

UCS-4

UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个plane。每个plane根据第3个字节分为256行(rows),每行包含256个cells。当然同一行的cells只是最后一个字节不同,其余都相同。
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节为0的码位被称作BMP。
将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。在UCS-2的两个字节前加上两个零字节,就得到了UCS-4的BMP。而目前的UCS-4规范中还没有任何字符被分配在BMP之外。

编码转换

请参考
字符编码之UCS-2与Utf-8
UCS-2和UTF-8的互相转换

操作系统使用编码

从Windows2K开始,Win的系统内核开始完全支持并完全应用Unicode编写,所有ANSI字符在进入底层前,都会被相应的API转换成Unicode。
Linux 服务器上 UCS-2 编码方式与 Winodws 不一致,以下是有关两个平台 UCS-2 编码的潜规则:

  1. UCS-2 不等于 UTF-16。 UTF-16 每个字节使用 ASCII 字符范围编码,而 UCS-2 对每个字节的编码可以超出 ASCII 字符范围。UCS-2 和 UTF-16 对每个字符至多占两个字节,但是他们的编码是不一样的。
  2. 对于 UCS-2, windows 下默认是 UCS-2LE。用 MultibyteToWidechar(或者A2W)生成的是 UCS-2LE 的 unicode。windows记事本可以将文本保存为 UCS-2BE,相当于多了层转换。
  3. 对于 UCS-2, linux 下默认是 UCS-2BE。用iconv(指定UCS-2)来转换生成的是 UCS-2BE 的 unicode。如果转换windows平台过来的 UCS-2, 需要指定 UCS-2LE。
  4. 鉴于windows和linux等多个平台对 UCS-2 的理解不同(UCS-2LE,UCS-2BE)。MS 主张 unicode 有个引导标志(UCS-2LE FFFE, UCS-2BE FEFF),以表明下面的字符是 unicode 并且判别 big-endian 或 little-endian。 所以从 windows 平台过来的数据发现有这个前缀,不用慌张。

对于JAVA/.NET/Python等这些“新”的语言来说,内置的字符串所使用的字符集已经完全是Unicode最重要的是,世界上大多数程序用的字符集都是Unicode,因为Unicode有利于程序国际化和标准化。

Python与编码

我会接着写另外一篇文章。

参考资料

    1. http://blog.chinaunix.net/uid-12348673-id-3335304.html
    2. http://demon.tw/programming/utf-16-ucs-2.html
    3. http://cryolite.iteye.com/blog/505011
    4. http://blog.chinaunix.net/uid-10468429-id-2953054.html

编码发展历史