什么是字符集和编码

文本文件中,为了让比特表示文字,我们发明了字符集,让特定的比特序列对应特定的字符。

常见的字符集有:

ASCII

计算机诞生于美国,因此最早的时候,人们只需要表示英文 26 个字符在内的少数字符,用半个字节就可以表示完这些字符。这就是 ASCII 字符集,最高位为 0,其余 7 位可以表示的范围是:0~127。总共 128 个字符。

ASCII 字符分为:控制字符(不可显示) 和 可显示字符

其中,0x20 以下为控制字符,不可显示。比如,0x10 表示换行,0x07 表示发声。另外还有个特殊的控制字符:0x7f 表示删除,所以总共是 33 个控制字符,95 个可显示字符。

EASCII

欧洲国家使用计算机之后,也有了创建字符集的需求,于是对 ASCII 进行扩展,使用了剩下一半字节的空间,0x80 - 0xff的定义就被填充成了希腊字母,罗马字母等符号。

此时,编码依旧控制在 8 位以内,相安无事。

GB2312

GB 是国标的意思

中国开始使用计算机,于是有了这个字符集,由于中文是象形文字,如果每个字符一个键的话,肯定放不下:

1555825210135.jpg

但我们有拼音,所以直接用美国键盘就行了。

GB2312 共收录了 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。它所收录的汉字已经覆盖中国大陆 99.75%的使用频率。

GB2312 的基本思想很简单,如果一个字符值为 127 及以下,那它就是一个单字节字符,和 ASCII 兼容;如果一个字符值为 127 以上,那它和后面的那个字符组成一个汉字。(同时,后面那个字符也一定是 127 以上的)

通过这个方式,我们扩展出来了 7000+的简体汉字,同时还把日本假名,罗马希腊字母,数学符号也容纳了进来。

同时还产生了一个新的概念,全角字符:ASCII 码里本身有的字符,也被我们扩展成了双字节字符。为了区分,前者称为半角字符,后者称为全角字符。比如逗号、冒号、引号等等都有半角和全角之分。

至此,中文也可以在计算机上表示了。

GBK

K 是扩展的意思

7000 个简体汉字并不能把汉字穷举了,一些生僻字并不包含在 GB2312 里。

GB2312 表示,我可能还可以抢救一下,我还有另一半的潜力没有发挥呢!

这另一半的潜力就是,第二个字节的0x00 - 0x7f部分。这部分空出来,是由于 GB2312 表示汉字时,要求两个字节都是在0x80 - 0xff范围内的。

填上这部分的空档之后,再生僻的字也被表示出来了,同时,我们还考虑到港澳台同胞的感受,包含了繁体字,一共大概增加了 20k 的汉字和符号。

Unicode

随着时间的推移,互联网的兴起,不止海峡两岸,全世界范围内共建一个字符集的呼声越来越高。

Unicode,统一了所有地区的字符,且还在不断扩充中。Unicode 编码系统可分为 编码方式实现方式 两个层次。Unicode 的实现方式称为 Unicode 转换格式(Unicode Transformation Format,简称为 UTF)

UTF-8 兼容 ASCII,UTF-16 不兼容 ASCII。

UTF-8

前面说的都是字符集,UTF-8 却是一种编码方式,因为它并不是去做字符集的事情,而是为的便于 Unicode 码的传输和存储而生的。UTF-8 兼容 ASCII 编码,所以应用非常广泛,几乎已经是互联网标准。

与其他的编码方式(如哈夫曼编码)的思想一致,UTF-8 的原则就是,使用不定长字节(1-6 字节)来表达一个字符,使用频率越高的字符,字节数越少。这样就能最大程度上节约空间。具体的编码方式如下:

1
2
3
4
5
单字节字符:
以0开头,后面7位表示字符,事实上,UTF-8的单字节字符就是ASCII字符,完美兼容;
n字节字符:
第一个字节的前n位为1,第n+1位为0。读到此字节时,可以方便的知道后续多少字节是用来表示一个字符;
其余字节,以10开头。
1
2
3
4
5
6
7
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
—————————————————————–
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

实际上从第二行对照开始,就存在空间浪费,因为右边的 x 的位数足够用来表示 0 到左边的第二个数所表示范围内的所有数,每行浪费的空间大小是左边的第一个数。

UTF-8 和 Unicode 是一一对应的,对于常用汉字,基本上都是占用 3 个字节,生僻汉字可能占用到 6 个字节。对于 GB2312 和 GBK 来讲,UTF-8 无疑造成了浪费,所以,UTF-8 可以说是对英文友好,但对中文不友好的一种编码方式。所以在中文界,GB2312 与 GBK 依旧有自己的市场。

ANSI 编码

ANSI 并不是确定的一种编码,在简体中文操作系统指的是 GB2312,在繁体操作系统指的是 BIG5。

Windows 里说的「ANSI」其实是 Windows code pages,这个模式根据当前 locale 选定具体的编码,比如简中 locale 下是 GBK。把自己这些 code page 称作「ANSI」是 Windows 的臭毛病。在 ASCII 范围内它们应该是和 ASCII 一致的。

字符集(character set)与编码(encoding)的区别

对于 ASCII、GB 2312、Big5、GBK、GB 18030 之类的遗留方案来说,基本上一个字符集方案只使用一种编码方案。

比如 ASCII 这部标准本身就直接规定了字符和字符编码的方式,所以既是字符集又是编码方案;而 GB 2312 只是一个区位码形式的字符集标准,不过实际上基本都用 EUC-CN 来编码,所以提及「GB 2312」时也说的是一个字符集和编码连锁的方案;GBK 和 GB 18030 等向后兼容于 GB 2312 的方案也类似。

于是,很多人受这些遗留方案的影响而无法理解字符集和编码的关系。

对于 Unicode,字符集和编码是明确区分的。Unicode/UCS 标准首先是个统一的字符集标准。而 Unicode/UCS 标准同时也定义了几种可选的编码方案,在标准文档中称作「encoding form」,主要包括 UTF-8、UTF-16 和 UTF-32。

所以,对 Unicode 方案来说,同样的基于 Unicode 字符集的文本可以用多种编码来存储、传输。所以,用「Unicode」来称呼一个编码方案不合适,并且误导。

字节顺序标记(BOM)

这里涉及到一个 字节序 的概念,请先了解这个概念。

BOM(Byte-Order Mark):一种为了跨平台设计的文件起始标记,但很多程序没去处理这个,用了 BOM 反而常造成问题。

在 UTF-16 中,字节顺序标记被放置为文件或字符串流的第一个字符,以标示字节顺序。

  • 大端:0xFEFF
  • 小端:0xFFFE