厘清概念: 字符集, 编码, 代码页

为什么要厘清概念, 因为这些概念到处被滥用, 但是见到的场合又实在太多, 见到的多, 有可能让人更加模糊而不是更加清晰.

编码(encoding)

这是最被滥用的概念, 在Jole的文章中, 用UTF8来表示Unicode被称之为encoding. 实际上encoding的含义远不止这些. 本质上encoding就是决定一个字符应该用什么数字表示. 从计算机的角度看, 根本不存在字符的概念, 有的仅仅是数字, 无论存储在磁盘上, 显示在网页上, 传输在网络上, 都仅仅只有数字而已. 文字是数字被解释后的结果, 解释的方法是由人来决定的.

从广义的角度看, 计算机内的一切信息都有编码, 因为这些信息都是数字. 例如图片, 图片由像素组成, 表示一个像素有很多种方法, 如果一副图片只有黑白两色, 那么只需要两个数字, 一个数字表示黑色, 一个数字表示白色, 然后按矩形排列就可以得到一副黑白图像. 同样的一副图片可以保存为各种格式, 他们表示像素的方法都不一样. 所以, 将信息用数字来表示, 存储, 传输就是编码, 而信息不限于文字, 可以是其他任何东西, 像素, 声音...

所以, 不要把encoding和某个特殊的名词例如UTF, Unicode联系起来, 只要在计算机中, 一切都是encoding.

字符集

前面说过了, 计算机只对数字感兴趣, 对其所表示的信息是毫无概念的. 而对人类来说, 数字是没有意义的, 人类关心的是这些数字表示的信息. 数字表示什么字符, 就是所谓的字符集. 同样的方法也适用于其他信息, 对于图片来说, 一个数字表示什么颜色, 这个颜色集合决定了数字所能表现的图像. 我们也可以叫他颜色集, 例如单色, 256色, 真彩色...

从上面的两个概念看, 编码和字符集根本就是同一个东西, 只不过从不同的角度来命名, 从计算机的角度叫做编码, 从人类的角度看叫字符集. 每个编码必然对应一个字符集. 同一个字符集可以对应多个编码. 因为人类的符号数量是有限的, 而数字的组合是无限的. 像ASCII这样的编码只是一种约定俗成, 理论上可以有无限多种方案来表示同一个字符集.

代码页

简单来说, 代码页就是字符集. 那么为什么起了另外一个名字, 而不直接叫做字符集? 反过来问一个问题, ASCII是否可以看成是美国人的代码页? 完全可以. 代码页就是针对某个地区制定的字符集及其编码. 因为美国人最先开始使用计算机, 他们认为26个字母加上一些标点符号已经完全足够, 而没有考虑到其他国家的人们也要使用计算机. 后来其他国家也开始使用计算机, 不得不制定自己的字符集. 多数时候一个地区只需要处理本国语言和文字即可, 因此没有必要一个包含所有文字的方案, 各个地区制定了自己的字符集编码方案. 彼此之间并不相通.

用一个例子来说明, GB2312是一个编码, 当然也是一个字符集. 同时对应一个代码页CP936. 代码页CP936只是GB2312的一个别名.

Unicode

有了上面的概念之后再来看其他的概念就比较清晰了. 例如Unicode, Unicode是一个字符集, 很多人谈到Unicode的时候, 会认为在这种编码中所有字符都占两个字节, 即使是一个英文字母也要占两个字节, 其中一个字节是0. 这是将Unicode字符集等同于一种编码了. 前面讲过, 同一个字符集不一定要用某种特定的编码才能表示, 理论上可以有无限种组合来表示同一个字符集.

Unicode收录了全世界绝大部分语言用到的符号. 将这些符合和特定的数字组合一一对应起来方案就是Unicode编码. 有多种方案, 例如UCS2, UCS4等. 谈到Unicode字符集的时候, 含义是明确的, 但是谈到Unicode编码却是模糊的. 在不同的场合下就有不同的含义. 通常Unicode编码是指UCS2格式的编码方案.

UTF8

UTF8被滥用的也很厉害, 有些地方UTF8叫做一个字符集, 另一些地方叫做编码. 在MySQL数据库中建表的时候指定charset, 用的是utf8. UTF8既不是字符集, 也不是编码. 而是对UCS2编码的再编码. 我们知道字符集编码是将数字映射为字符, 而UTF8则是将数字映射为数字, UTF8虽然也和Unicode字符集中的字符一一对应, 但不是直接对应, 而是首先对应到UCS2编码, 从而间接的对应到字符. 操作系统拿到一个UTF8编码的数字的时候, 没办法立即知道他对应哪个字符, 必须先转换为UCS2编码, 然后根据Unicode规范得知表示的是哪个字符.

ANSI 与 ASCII

这两个也经常混淆. ASCII, 是一个编码, 用一张ASCII表来表示, 当然, 这个编码同时定义了一个字符集. 但是对于ASCII, 我们更侧重他编码的一面, 当讨论Unicode的时候, 字符集是确定的, 但是编码却可以有多种形式 . 而讨论ASCII的时候编码是确定的, 从而间接的确定了字符集. 设计ASCII显然和设计Unicode不同, 设计Unicode是先想好了字符集, 然后想办法用编码来表示这个字符集. 而ASCII是在使用的过程中约定俗成而形成的. ASCII中只有127个数字可以使用, 只能定义127个字符, 选择把哪些字符包括进来是一个权衡取舍的过程. 只有最常用, 最必要的字符才能包括进去.

ANSI(美国国家标准委员会), 这个词的含义混乱和微软有莫大的关系. ANSI是对ASCII的扩展, 8bit的编码, 增加了一些西欧的字母. 显然ANSI不会仅仅制定字符编码的标准, 但是在计算机术语中, ANSI已经特指ANSI所制定的字符编码标准.

那么ANSI究竟是什么? ANSI的初衷是扩展ASCII编码, 因此可以说是一种编码, 但是各种不同的扩展ASCII的编码都冠以ANSI的名称, 例如微软操作系统中对ANSI的实现是代码页1252. 后来其他国家地区指定了自己的代码页编码标准, 在微软操作系统中统统冠以ANSI的字样. 那么ANSI实际上成了一个大杂烩, 不是某种编码, 也不代表某种字符集. 这样在使用的时候就出现了很多含义, 有时候, 他指对ASCII扩展的编码方案, 有时候指微软的1252代码页的实现, 有时候, 在各个国家本地, ANSI指的是那个地方的代码页. 对于中国的用户, 提到ANSI, 指的就是cp936, 而如果是美国用户, 指的就是代码页1252.

SBCS, DBCS, MBCS

这些东西都是ANSI的不兼容性引起的, 因为ANSI是一个大杂烩, 有些ANSI字符编码只用一个字节, 有些用2个字节, 有些用变长字节, 那么度量字符串的长度就成了问题, 一个字符不一定就等于一个字节. 那么编写程序的时候对于不同的ANSI编码就必须用不同的算法来计算字符串的长度. 由于MBCS的复杂, 基本已经不用, 一般的ANSI都是DBCS, 例如cp936. ASCII以及ANSI里面使用单个字节的编码都可以像以前一样处理. 如果采用Unicode, 这些就没有存在的必要了.

总结

任何时候谈到编码, 或者字符集的时候, 必须想到存在一张表, 就像ASCII表那样, 将数字的组合和字符对应起来. 否则就不要贴上字符集或者编码的标签. 例如ANSI, 如果没有指定代码页, ANSI就不代表特定的字符集和编码 . 例如utf8, utf8不是一个字符编码, 而是Unicode编码的再次编码.

从历史趋势看, 首先是ASCII, 没有兼容问题, 然后有了ANSI, 产生了大量的不兼容, 最后产生了Unicode, 随着utf8的流行, 再次变得统一.