字符集简史

DinS          Written on 2017/7/14

一、字符集的概念

计算机中使用编码表示字符,一个特定的编码对应一个特定的字符,即一种映射关系。
于是问题就来了:哪个编码对应哪个字符?
根据映射的方式,有许多不同的编码方案,这就是字符集产生的原因。
先打预防针:这个问题比你想象的要复杂

二、ASCII

ASCII全称American Standard Code for Information Interchange。
计算机最初发源于美国,美国和西欧都是拉丁语系,使用字母表来表达语言,字母表中字母的个数是很有限的,于是美国国家标准学会(American National Standard Institute , ANSI )制定了标准的单字节字符编码方案。起始于50年代后期,在1967年定案。该方案即ASCII码表,后来逐渐扩展到全世界,成为了一个难得的权威标准。

该方案很简单,使用7位二进制数来表达可能的128种字符。
比如说二进制1000001,转化为十进制是65,其对应的字符是A。
具体各种映射可以查阅ASCII码表。

写程序可以看到这一点,有如下代码:

输出结果:

ASCII是最最基础的字符集,所有其他字符集都(必须)兼容该字符集。
之后ASCII字符集又进行了扩展,多使用了一个比特位,于是变成8位二进制数表达256种字符。但是这种扩展并没有真正的标准化。

三、多字节字符集

ASCII编码方式简单直接,受到了欢迎。但是很快就出现了困境。
随着计算机的普及,各个国家都使用上了计算机,但是各个国家的语言是不同的,现有的ASCII只能处理最基本的英语字符,这是一对矛盾。
各个国家采取的普遍做法是以ASCII 127为基础,兼容ASCII 127,使用大于128的编码作为一个Leading Byte,紧跟在Leading Byte后的第二(甚至第三)个字符与Leading Byte一起作为实际的编码,映射到唯一字符。

由于这种方式使用了不止一个字节,所以称为多字节字符集,英文是Multi-Byte Chactacter System。比如中文字符集使用的就是称为GB-2312的多字节字符集,其利用两个字节拼成一个汉字。

下面写代码做个试验。

前面两句输出字符对应的16进制,后半部分将16进制拼成两个字节输出。

结果如下:

说明:’A’的16进制是41,对应十进制是65。与上例一致,这说明多字节字符集兼容ASCII。
‘你’的16进制是c4e3,这说明是由两个字节拼成的,其leading byte是c4,对应十进制为196,超出了ASCII的范围。

知道了‘你’的16进制后我们可以反过来使用。
因为一个char是一字节,所以定义了两个char,first表示leading byte,second表示后续字节。分别赋值‘你’的两个字节。

最有意思的是输出部分,不管用cout还是printf,可以看到代码中连续输出了两个char类型,但是看到的结果是一个‘你’字。
结果是符合我们预期的,但这里就有问题了,为什么不是单独输出两个字符?
在这里操作系统替我们做了工作,当输出的char超出了ASCII范围后,操作系统会自动把后续的一个char与第一个char拼成双字节字符。
利用这个双字节字符去查双字节编码表,如果找到了对应字符就输出,否则就按两个单字节输出。

四、Unicode字符集

多字节字符集解决了各国语言显示的问题,但同时又引入了新问题(技术的发展总是这样的规律)。
新问题在于兼容性。既然多字节字符集是各个国家自己扩充ASCII码的结果,这个过程没有协调,那么必然会发生映射冲突的问题。具体而言,在汉字编码表中c4e3代表‘你’,但是在阿拉伯语编码表中c4e3代表却代表另一个字符
假如现在有一个软件既要显示中文又要显示阿拉伯文,那么这个c4e3代表什么呢?

为了解决多字节字符集之间相互冲突的问题,Unicode应运而生。
所谓Unicode,就是每个字符都有一个唯一的编码。Uni前缀表示单一,code表示编码。
Unicode本身也有自己的发展历史。
首先出现的是双字节Unicode,用16个二进制位对字符进行编码,一共能够表示65536个字符,称为utf-16
后来发现大多信息都是英文构成的,为了节省空间,同时为了兼容单字节的处理系统,就出现了一种变种的unicode字符集——utf8。utf8的实现原理和多字节字符集一样,前面128位符合ASCII标准,后面的采用变长的编码方式,就是一个字符可能用两个字节、三个字节或四个字节进行编码。稍后还会具体讲解。
再后来发现16位也不够用了,比如想表示一些古代历史上的现在已经消亡了的语言的字符,又出现了utf-32。

Unicode与多字节字符集最大的区别在于对所有字符“一视同仁”。
比如汉字不再使用两个字节拼接而成,而是(概念上)使用一个unicode表示。
为了使用unicode需要在代码上做出一定调整。
下面是一个示例。

结果如下:

在这里可以明显看到两个‘你’的16进制不一样,这就是多字节字符集和unicode的本质区别。

但是结果并不正确,最后两条输出并不符合我们的预期。如果想要正确输出,需要再添加一行代码。

现在结果正确了。

仅仅了解了字符集的历史发展脉络并不能帮助程序员编程,实际上我们最经常做的一件事:读取文本文件,里面都有许多坑。请看下一篇文章《读取文本文件 – 没有你想象的那么容易