在计算机内部,所有信息最终都是一个二进制值,每一个二进制位(bit)有0
和1
两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte)。也就是说,一个字节一共可以用来表示256种不同的状态,每一个状态对应一个符号,就是256个符号,从00000000
到11111111
。
一、ASCII 码
ASCII码是上个世纪60年代美国制定的一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。
ASCII 码一共规定了128个字符的编码,比如空格SPACE
是32(二进制00100000
),大写的字母A
是65(二进制01000001
)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0
。
编码对照如下表所示:
二、非 ASCII 编码
英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,汉语中常用的文字就有几千个,并且不同的国家有不同的字母和文字,即使它们都使用256个符号的编码方式,代表的字母也是不一样的。比如,130在法语编码中代表了é
,在希伯来语编码中却代表了字母Gimel
(ג
),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。
三. Unicode码
随着世界各国的沟通交流不断增加,由于编码方式不同造成的解析问题日益严峻,由于存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。导致如果想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码的现象。电子邮件常常出现乱码,就是因为发信人和收信人使用的编码方式不一样。
这个时候Unicode码抱着统一世界各国的编码方式的梦想横空问世。Unicode 是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639
表示阿拉伯字母Ain
,U+0041
表示英语的大写字母A
,U+4E25
表示汉字严
。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
Unicode 只是一个符号集(主要为UCS-2字符集和UTF-4字符集),它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字严
的 Unicode 是十六进制数4E25
,转换成二进制数足足有15位(100111000100101
),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。这导致了以下问题:
如何才能区别 Unicode 和 ASCII ?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?如果 Unicode 统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0
,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这样会被接受吗?
最终造成的结果是:出现了 Unicode 的多种存储方式,并且Unicode 在很长一段时间内没有被广泛接受,直到互联网的普及。
四、UTF-8
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。
UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0
,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2)对于n
字节的符号(n > 1
),第一个字节的前n
位都设为1
,第n + 1
位设为0
,后面字节的前两位一律设为10
。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
五、Unicode 与 UTF-8 之间的转换
Windows平台,有一个最简单的转化方法,就是使用内置的记事本小程序notepad.exe
。打开文件后,点击文件
菜单中的另存为
命令,会跳出一个对话框,在最底部有一个编码
的下拉条。选择完"编码方式"后,点击"保存"按钮,文件的编码方式就立刻转换好了。
里面选项内容有:ANSI
,UTF-16LF
,UTF-16BE
和UTF-8
。
1)ANSI
是默认的编码方式。对于英文文件是ASCII
编码,对于简体中文文件是GB2312
编码(只针对 Windows 简体中文版,如果是繁体中文版会采用 Big5 码)。
2)UTF-16LF
实际为Unicode
编码,这里指的是记事本使用的 UCS-2 编码方式,即直接用两个字节存入字符的 Unicode 码,这个选项默认 little endian 格式即UTF-16LF
。
3)UTF-16BE
编码与上一个选项相对应。
4)UTF-8
编码,也就是刚谈到的编码方法。
在对编码进行存储时,第一个字节在前就是 Big endian 方式即UTF-16BE
;第一个字节在后就是 Little endian 方式即UTF-16LF
穿插内容:这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-endian)敲开还是从小头(Little-endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。
第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。
此处解答之前的遗留问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用FEFF
表示。这正好是两个字节,而且FF
比FE
大1
。
如果一个文本文件的头两个字节是FE FF
,就表示该文件采用大头方式;如果头两个字节是FF FE
,就表示该文件采用小头方式。
六、GB2312及GBK编码规则
再介绍一下简体中文常见的编码方式 GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示 256 x 256 = 65536 个符号。
GB2312编码是第一个汉字编码国家标准,是由中国国家标准总局1980年发布,1981年5月1日开始实施的一套国家标准,标准号是GB2312—1980。GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。GB2312编码共收录汉字6763个,其中一级汉字3755个,二级汉字3008个。同时,GB2312编码收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个全角字符。
GB2312编码范围:A1A1-FEFE,
其中汉字编码范围:B0A1-F7FE,
汉字编码: 第一字节0xB0-0xF7(对应区号:16-87),
第二个字节0xA1-0xFE(对应位号:01-94)。
2字节编码:高位为0xA1-0xF7,低位为0xA1-0xFE
特殊符号编码:高位为0xA1-0xA9,低位为0xA1-0xFE
GB2312编码采取分区表示方式:
GB2312编码对所收录字符进行了“分区”处理,共94个区,每区含有94个位,共8836个码位。这种表示方式也称为区位码。
01-09区收录除汉字外的682个字符 |
---|
10-15区为空白区,没有使用 |
16-55区收录3755个一级汉字,按拼音排序 |
56-87区收录3008个二级汉字,按部首/笔画排序 |
88-94区为空白区,没有使用 |
举例来说,“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601。
双字节编码方式:
GB2312规定对收录的每个字符采用两个字节表示,第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。所以它的区位码范围是:0101-9494。区号和位号分别加上0xA0就是GB2312编码。例如最后一个码位是9494,区号和位号分别转换成十六进制是5E5E,0x5E+0xA0=0xFE,所以该码位的GB2312编码是FEFE。