字符集与字符编码

字符集(Charset)

是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。

简单的说字符集相当于一种映射关系,一个种类的集合中,把每个字符分配一个唯一的、一一对应的编号。

举个例子比如:

编号编号表示字符
1A
2B
3C
26Z
27[
28\

像例子中这样,一组映射想要表示的字符与其编号的集合就叫字符集

不同的字符集有不同的映射,比如 ASCII 字符集 的映射中, 映射 A-Z 的编号就是 65-90,与上面例子中的编号就不一样,这就是两个不兼容的字符集。

字符编码(Character Encoding)

在上面过程中,我们已经给每个字符都分配了一个唯一的编号,但是我们要怎么把打出来的一段文字存到文件中去呢?你可能会觉得的奇怪,这还不简单,计算机是存储的二进制文件,我们把文字中每一个字符编号数字对应的十六进制保存为一个字节,按照字节把文字中的所有字符编号依次写入到文件中不就完了么?

额,这就会出问题了。上面我们是以 ASCII 字符集来举例的,ASCII 字符集将字母、数字和其它符号编号,并用 8 比特的二进制来表示这个整数;因为每个字符都只有一个字节,所以看起来好像没什么问题;那我们再来考虑下其他的一些字符集。

比如说中文的 GBK 字符集中部分:

编号编号表示字符
45217
45218
45219

这些编号超过 255 的字符,显然不能用一个字节来表示了,最少也要两个字符。再来按照上面的说法,把它们编号对应的十六进制依次写进文件中,假如这段文本中既有中文又有英文,那么问题来了:

我们在读取文件的时候,读到了 4 个字节,那它究竟是表示两个汉字,还是四个英文字母,还是一个汉字两个字母呢?

看吧,不加限制的直接保存编号会产生一定的歧义。为了消除这种歧义,方便计算机能够简单的识别和存储字符,我们引入一种 编码规则,比如 所有字符编号对应的十六进制均按两字节存储,不够两字节的编号在前面添加 0x00 来补齐两字节。这样就解决了上面的歧义问题,所有字符都储存为两字节,每次读取两字节来表示一个字符,包括英文也是;这就是一种典型的 双字节定长编码

嗯,歧义的问题是解决了,但是又有新的问题,上面我们是说至少要两个字节的字符集,要是哪天我们遇到一个要四个字节才能表示字符的字符集,那就得使用 四字节定长编码,这里就存在巨大的浪费。我们这个字符集中前十分之一编号的常用字符时,本来只需要一个字节来表示的字符,现在统统使用四个字节,一篇文章的体积无形中变大了三倍,这就 极大的浪费了存储空间

于是人们就在想,能不能让原来只需要一个字节表示的字符编号,现在还是用一个字节来储存,原来只需要两个字节表示的字符编号,还是用两个字节来储存,依次类推,都只用其能表示的最小编码长度呢?

答案是有的,于是有了 变长字符编码

上面字符集处我们讲到过,每个字符有唯一的,一一对应的编号,在编码后也应如此。这就要求 每个字符有唯一编码,每个码字是唯一可译的

《信息论》 中指出,只有异前置码是唯一可译码。如果一个码的任何一个码字都不是其他码字的前缀,则称该码为异前置码(前缀码)。异前置码的充要条件是满足克拉夫特不等式(Kraft Inequality),即:
$$
\sum_{i=1}^n m{-k_i} \leq 1
,
\text{ ($m$ 为码元数,$k_i$ 指第 $i$ 个码元长度)}
$$
我们来看一个常见的变长编码(UTF-8)的部分(前三字节):

Byte0Byte1Byte2
0xxxxxxx
110xxxxx10xxxxxx
1110xxxx10xxxxxx10xxxxxx

在 ASCII 码的范围,用一个字节表示,超出 ASCII 码的范围就用字节表示,这就形成了我们上面看到的 UTF-8 的表示方法,这様的好处是当 Unicode 文件中只有 ASCII 码时,存储的文件都为一个字节,所以就是普通的 ASCII 文件无异,读取的时候也是如此,所以能与以前的 ASCII 文件兼容。

大于 ASCII 码的,就会由上面的第一字节的前几位表示该 Unicode 字符的长度,比如 110xxxxx 前三位的二进制表示告诉我们这是个 2Bytes 的 Unicode 字符;1110xxxx 是个三位的 Unicode 字符,依此类推;xxx 的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头1的数目就是整个串中字节的数目。

上面提到的 Unicode 就是一种伟大的编码方案标准!Unicode 是计算机科学领域里的一项业界标准,它为让计算机方便的使用统一而兼容的编码表达任意语言的任意字符、用以取代现有的字符编码而设计,也被称为 万国码、统一码。Unicode 标准涵盖的内容很宽,除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。

我们常说的 Unicode 指的是 Unicode 字符集,是将世界上大部分国家的文字做了整理、编排,不断发展更新的多个文字种类平面的编号集合;Unicode 字符集的编码有多种,其实现方式都称为 Unicode 转换格式 (Unicode Transformation Format,简称为 UTF)。

UTF-8 就是只 Unicode 字符集 实现中的一种字符编码方式。

字符集与字符编码的混称

总的来说,简单的理解, 字符编码就是字符集这种映射编号具体到计算机处理时,所用编码规则的实现方式。

比如 Unicode 字符集的字符编码方式就有 UTF-8、UTF-16、UTF-32 等。

但是也有很多我们常说的字符编码标准中,即包含了字符集也包含了字符编码,比如 ASCII 字符集ASCII 字符编码GB2312 字符集GB2312 字符编码GBK 字符集GBK 字符编码 等;

于是人们也常把它们混称,将字符集和字符编码都用标准方案的名称来统称,比如 ASCII 编码GBK 编码GB2312 编码。但是我们在谈论的时候,应该自己根据话语环境分辨其具体指代意思而不至出现歧义。

现代编码模型

在 Unicode 技术报告 Unicode Technical Report (UTR) 中,将现代编码模型分为五个层次:

  1. 抽象字符表(Abstract character repertoire)是一个系统支持的所有抽象字符的集合。字符表反映了如何将书写系统分解成线性信息单元。例如拉丁、希腊字母表分为字母、数字、标点这样的一些字符,它们都能按照一种简单的线性序列排列,字符表包括预先编号的字母和符号的组合。
  2. 编码字符集(CCS: Coded Character Set)是将字符集 $\displaystyle C$ 中每个字符映射到一个坐标(整数值对:x, y)或者表示为一个非负整数 $\displaystyle N$ 。字符集及码位的映射称为编码字符集。多个编码字符集可以表示同样的字符表,由此产生了编码空间(encoding space)的概念:简单说就是包含所有字符的表的维度。编码空间还可以用其子集来表述,如行、列、面(plane)等。编码空间中的一个位置(position)称为 码位(code point)。一个字符所占用的码位称为 码位值(code point value)。编码字符集就是把抽象字符映射为码位值。
  3. 字符编码表(CEF: Character Encoding Form),也称为 storage format,是将编码字符集的非负整数值(即抽象的码位)转换成有限比特长度的整型值(称为 码元 code units)的序列。对于定长编码来说是个到自身的映射(mapping),但对于变长编码来说,该映射比较复杂,即把一些码位映射到一个码元,把另外一些码位映射到由多个码元组成的序列。
  4. 字符编码方案(CES: Character Encoding Scheme),也称作 serialization format。将定长的整型值(即码元)映射到 8 位字节序列,以便编码后的数据的文件存储或网络传输。例如在使用Unicode的场合,使用一个简单的字符来指定字节顺序是大端序或者小端序(UTF-16BE, UTF-16LE)。
  5. 传输编码语法(transfer encoding syntax),用于处理上一层次的字符编码方案提供的字节序列。一般其功能包括两种:一是把字节序列的值映射到一套更受限制的值域内,以满足传输环境的限制,例如 Base64;另一是压缩字节序列的值,如 LZW 或者进程长度编码等无损压缩技术。

参考资料