前言

计算机只能存储二进制的数据,所以字符也一样只能通过将字符映射为相应的二进制形式才能保存,读取的时候由系统对字符进行图形渲染。

不同的映射方式导致了不同的 字符集 [1](character set),譬如说,「雪」字在 GBK 编码中对应的是「D1A9」,在 Unicode 编码中对应的是「96EA」。然而字符集只是规定了字符与二进制之间的映射,并没有规定具体如何实现,这个责任由 字符编码 (Character Encoding)承担,字符集与字符编码可能不同。

ASCII

最初的时候,美帝只考虑了自己需要的英文,26 个大小写字母加数字符号控制字符乱七八糟一通搞,正好 128 个字符,一个字节表达完毕,这就是 ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)。后来计算机传到欧洲,字符不够用,为了对剩下的 128 个字符进行了利用并制定标准,ISO 推出 ISO 8859,显然 256 个字符也是不够这些国家用的,所以 8859 被分成了十几个部分,它覆盖了大部分使用拉丁字母的语言文字。

代码页

在 ISO 标准完全定型之前,IBM 就有一系列自己的字符编码,叫做代码页(code page),比如 437(扩展 ASCII)、850(西欧语言)、852(东欧语言)。IBM代码页通常被用于控制台(console)环境,也就是 MS-DOS 或 Unix Shell 那样的命令行环境。
微软将 IBM 代码页称为 OEM 代码页,自己定义的称为 ANSI 代码页, 比如 1252(西欧语言)、1250(东欧语言)、936(GBK 简体中文)、950(Big5 繁体中文)、932(SJIS 日文)、949(EUC-KR 韩文)等。

GB 家族

当计算机来到中国,这个问题的难度升级了,一个字节无论如何也表达不了博大精深的汉字,于是人们拿两个字节解决了这个问题,也就是 GB 2312 字符集,它是一个 94 * 94 的表,包括 7445 个字符。GB 2312 兼容 ASCII,GB 2312 编码对汉字/符号进行了分区(区位码)。每个汉字 / 符号以两个字节来表示。第一个字节称为「高位字节」,第二个字节称为「低位字节」。GB 2312 还有另一种编码方式 HZ,只是不常用。

GB 2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75% 的使用频率。但对于人名、古汉语等方面出现的罕用字和繁体字,GB 2312不能处理,譬如说,某位领导人的名字写不出来,于是人们对 GB 2312 进行了扩展,这就是 GBK,K为汉语拼音 Kuo Zhan(扩展)中「扩」字的声母。GBK 全称是汉字内码扩展规范(Chinese Internal Code Extension Specification)。GBK 有一字节和双字节编码,这里有个事情就是计算机如何知道当前的字节是独立的字符还是跟相邻的字符共同表示一个字符,GBK 是通过第一个字符的范围来辨别的,007F 范围内是一个字节,81FE 范围内是两个字节,GBK 向下完全兼容 GB 2312,GBK 与 CP936 大体相同,比它多 95 个字符。然而 GBK 但是毕竟只是规范,不是标准,随后国家推出 GBK 18030 以取代 GBK,它完全兼容 GB 2312,基本兼容 GBK,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。

UCS / Unicode

UCS

为了使混乱的编码格局得到统一,ISO 于 1990 年推出了通用字符集(Unicode Character Set,UCS),它包含了一百多万个字符,UCS 有两种编码方式:UCS-2 和 UCS-4,分别用两个字节和四个字节表示一个字符,UCS-2 只能表示 65536 个字符,明显不够用,已经过时了。UCS-4 根据最高位为0的最高字节分成27=128 个 group。每个 group 再根据次高字节分为 256 个 plane。每个 plane 根据第 3 个字节分为 256 行(rows),每行包含256个 cells。当然同一行的 cells 只是最后一个字节不同,其余都相同。group 0 的 plane 0 就是 BMP。

Unicode

ISO之外还有另外一个组织:统一码联盟(The Unicode Consortium),它于1991年推出了 Unicode 1.0。后来与 ISO 组织合并成果。
Unicode 全称 Universal Multiple-Octet Coded Character Set。Unicode 的码空间从 U+0000 到 U+10FFFF, Unicode 的码空间可以划分为 17 个平面(plane),每个平面包含 216(65,536) 个码位。每个平面的码位可表示为从 U+xx0000 到 U+xxFFFF,其中 xx 表示十六进制值从 00H 到 10H,共计 17 个平面。如果 xx 是 0,即第 0 平面,可省略不写,第一个 Unicode 平面(码位从 U+0000 至 U+FFFF)包含了最常用的字符,该平面被称为基本多语言平面(Basic Multilingual Plane),缩写为 BMP。其他平面称为辅助平面(Supplementary Planes)。
这里字符集跟字符编码的区别就出现了,Unicode 只是指定了字符的映射,并没有指定实现方式,出现了 UTF-8、UTF-16 和 UTF-32 三种编码方式。

UTF-16

UTF-16 可以看作是 UCS-2 的父集(严格的说这不正确,在 UTF-16 中从 U+D800 到 U+DFFF 的码位不对应于任何字符,而在使用 UCS-2 的时代,U+D800 到 U+DFFF 内的值被占用),为什么说它是父集呢,因为当字符不在 BMP 时 UTF-16 会使用四个字节来编码,所以要注意,UTF-16 是变长编码,四个字节的表示算法比较啰嗦,我不写了。
UTF-16 的坑在于字节序(Endianness)的问题,就是存储和传输的时候哪个在高地址哪个在低地址,举个例子:「雪」的大端序(big-endian,也叫大尾序)为 U+D1A9,小端序(little-endian,也叫小尾序)为 U+A9D1。一般来说,以 Macintosh 制作或储存的文字使用大端序格式,以 Microsoft 或 Linux 制作或储存存的文字使用小端序格式,网络传输一般采用大端序。
为此,出现了三种解决方案,也就是UTF-16LE,UTF-16BE, UTF-16。UTF-16 是大端序还是小端序取决于在文件头是否有 BOM,如果没有就听天由命了,如果有的话,就说说 BOM,BOM(byte-order mark) 是字节顺序标记,它不仅仅存在于 UTF-16 中,只要编码方式受字节序影响,就需要 BOM,在 UTF-8、UTF-32、GB 18030 中也有它的身影,不过在不同的编码方式中表示也不一样。在 UTF-16 中用 U+FEFF 表示大端序,用 U+FFFE 表示小端序。

UTF-32

UTF-32 是 UCS-4 的子集,它对所有的字符都采用四字节表示,强迫症表示很开心。当然,它也逃不了与 UTF-16 类似的遭遇,它也分 UTF-32LE、UTF-32BE、UTF-32。

UTF-8

重头戏来了,如果一个文件 99% 都是英文,采用上述的 Unicode 编码方案太浪费硬盘了,如果是一个网站,流量也要翻倍啊,UTF-8 采用可变长编码,向下兼容 ASCII 编码,良好的解决了这类问题。

Unicode 和 UTF-8 之间的转换关系表 ( x 字符表示码点占据的位 )
码点的位数码点起值码点终值字节序列Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6
7U+0000U+007F10xxxxxxx
11U+0080U+07FF2110xxxxx10xxxxxx
16U+0800U+FFFF31110xxxx10xxxxxx10xxxxxx
21U+10000U+1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx
26U+200000U+3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
31U+4000000U+7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

Unicode 允许在 UTF-8 中使用 BOM,不过 UTF-8 的编码方式是字节序无关的,没必要使用 BOM。

记事本

打开记事本,文件->另存为,下方编码方式选项有四个

  • ANSI
  • Unicode
  • Unicode big endia
  • UTF-8

ANSI 看着好像 ASCII,但事实上,它的编码方式是系统默认的编码方式,对于一个 ANSI 文本,英文部分使用的就是 ASCII 编码,而中文部分使用的就是 GB 2312 编码,如果是繁体则会使用 BIG 5 编码。而第二个 Unicode 其实是带有 BOM 的小端序 UTF-16,最后那个 UTF-8 也是带 BOM 的。

注1:这是不严谨的说法,字符编码的层次不是简单的字符集与编码的映射,而是有五层模型。

参考:

文档信息

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
本文链接:www.snovey.com/2016/06/char-encoding.html