TOC

字符编码

从原理上来讲,我们的计算机其实只认识数字(要不然为什么叫做计算机),确切的说是 0 和 1,我们的文字信息存放在计算机中也是以数字形式存在。

所谓字符编码就是字符和数字之间的对应关系和转换规则。

ASCII

最早出现的字符编码就是 ASCII 美国信息交换标准代码,开发者(可能)一般叫阿斯克码。

ASCII 的参照对象是 IBM 为打孔机设计的 EBCDIC 编码(Extended Binary Coded Decimal Interchange Code, 拓展 BCD 交换码)。PS: 从名字都可以看出来,EBCDIC 的前身是 BCDIC。

八个二进制位,也就是八个 0 或者 1,我们叫做一个字节,是计算机信息存储的基本单位。
ASCII 码只使用一个字节的后面 7 位,0000000 ~ 1111111,也就是十进制的 0 到 127,一共 $2^7 = 128$ 个字符。具体的编码可以查看 ASCII 码表

ASCII 编码只考虑了英语的需要,其他语言无法表述。

EASCII

其他国家也开始纷纷设计本国语言的编码。
比如西欧国家设计的 EASCII(拓展 ASCII 编码),在兼容 ASCII 的基础上,利用闲置的那个二进制,设计了 128 - 255 共 128 个字符,一个字节的八位全部占满。主要是扩充了希腊字母,拉丁字母,符号。

img

GB2312

中国这边,1980 年推出了 GB/T 2312,采用两个字节表示一个字符,不考虑和 ASCII 的兼容,直接采用 33 - 126,也就是一共能支持 $94^2 = 8836$ 个字符。实际收入了六千多个汉字和几百个其他字符。

采用 33 - 126 是为了避开 ASCII 中的不可显示字符 0 - 31 、空格 32、删除符 127。这样的话,就算是纯 ASCII 环境,也能显示出内容来,可以用两个 ASCII 字符转换成一个汉字。

GB/T 2312 标准共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个字符。
GB/T 2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75%的使用频率。但对于人名、古汉语等方面出现的罕用字和繁体字,GB/T 2312 不能处理,因此后来 GBK 及 GB 18030 汉字字符集相继出现以解决这些问题。

GB13000

Unicode 项目从 1988 年开始,逐渐从少数几家 IT 企业组成的一个工作组,发展到成为一个国际字符标准的大联盟。PS: 1992 年,微软的技术规范将其称之为 Apple Unicode。
1993 年推出的 1.1 版成为 ISO 认定的国际标准,其中包含中文编码,确切的说,包含中日韩统一表意文字(CJK Unified Ideographs),很多地方都会称之为 CJK。
随后,中国政府发布 GB13000.1-93,认可了这项国际标准。
注意:Unicode 不是一种字符编码,而是一种字符集。比如 😂 (哭笑不得) 这个表情在 Unicode 中分配的码位是 128514,转换成十六进制的写法是 1F602,至于这个数字在电脑中如何表述 Unicode 是不关心的。Unicode 有几种具体的字符编码标准,比如 USC-2, UTF-8, UTF-16, UTF-32, 后面会讲到。

GBK

由于一开始没有 Unicode,跨国大厂为了协调各种不同编码,弄了一个叫做 CodePage 的东西,中文一般翻译为代码页。
我也不清楚这套机制,个人理解如下:

  1. 早期 IBM PC 需要显卡支持对指定字符编码的图形绘制,不同字符集的绘制规则存在不同芯片中,这些芯片就是代码页。
    由于是厂商提供的,所以又叫做 OEM 代码页。
  2. 图形化之后,操作系统提供字符绘制功能。但是在 Unicode 没有应用到操作系统设计之前,操作系统的内部编码在同一时间只能选择一种编码。
    这时代码页叫做 Windows 代码页,或者 ANSI 代码页。
  3. 后来 Windows 2000 开始(之前是 95、98,之后是 Windows XP),统一采用 Unicode 作为内部编码(内码)。但是 UI 交互中需要按照当地标准设置对应的编码。

简体中文在 Windows 中对应 CodePage 936,搞 Windows 开发的人应该都对 cp936 有点熟悉。
cp936 在 GB2312 码表的基础之上做了扩充,使之包含了 Unicode 1.1——也就是 GB13000——中的中日韩字符集,以及港澳台地区常用的 BIG5 编码(繁体中文)中的全部字符。
而且,它的设计就保持了和 ASCII 的兼容,和 EASCII 类似,在 ASCII 的闲置的最高位上做文章。如果读到一个字节的第一位是 1 就表示这是一个两字节字符。编码范围是 8140 - FEFE(排除 xx7F),一共包含 23940 个码位,也就是说能表示 23940 个字符。
1995 年 12 月 1 日,国家在 cp936 基础上制定了 GBK 编码(《汉字内码扩展规范(GBK)》1.0 版)。
注意:GBK 属于 “技术规范指导性文件”,不是标准。

GB18030

2000 年 3 月 17 日,质检总局(国家质量监督检验检疫总局,2018 撤销的国务院正部级直属机构,其职责划入市场监管总局)制定 GB18030 国家标准,是一种变长多字节编码方案,每个字符由一个、两个、或四个字节组成。

img

其对 GB 2312-1980 完全向后兼容,与 GBK 基本向后兼容,并支持 Unicode(GB 13000)的所有码位。GB 18030 共收录汉字 70,244 个。

GB 18030 主要有以下特点:

  1. 采用变长多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。
  2. 编码空间庞大,最多可定义 161 万个字符。
  3. 完全支持 Unicode,无需动用造字区即可支持中国国内少数民族文字、中日韩和繁体汉字以及 emoji 等字符。

GB 18030 在微软视窗系统中的代码页为 54936。

UTF-8

最成功的 Unicode 编码方案,变长多字节。
来自一个失败的操作系统项目 Plan9(另一个常见到这个名字的地方就是 Golang 汇编)。

咱们的中文在其中划在了三字节区,每个中文字符都需要三个字节来表示。

就好比中国的中,Unicode 码位为 4E2D,UTF-8 表示为 E4 B8 AD

'中'.encode('unicode-escape')
# b'\\u4e2d'

'中'.encode('utf-8')
# b'\xe4\xb8\xad'

注意:MySQL 中的 utf8 实现最多只支持三个字节。MySQL 的四字节方案为 utf8mb4
当年三字节确实够用了,不过随着 Unicode 的发展,UTF-8 已经需要 4 个字节才能完整表示所有 Unicode 字符。

Latin-1

标准名称: ISO/IEC 8859-1, 就是上面提到的 EASCII。

EASCII 的本意是拓展 ASCII(Extended ASCII),包含各种 ASCII 兼容编码(其中绝大多数可能都逐渐消失在历史中)。
由于语义太过宽泛,言之无物,人们一般不使用 EASCII 这个概念。
如果提到,一般是指 Latin-1。

Unicode 的前 256 个码位就是 Latin-1 字符。在 UTF-8 编码中,Latin-1 拓展的字符需要采用两个字节表示。

PS: 1998 年制定的 Latin-9 (ISO/IEC 8859-15 / Latin-0) 一般认为是对 Latin-1 的改进,替换掉了一些不常用字符,换取法语、芬兰语、爱沙尼亚语的完整支持,同时把 ¤(通用货币符号)换成 €(欧元符号)。

Windows CodePage 1252 是 Latin-1 的超集,是 Windows 在很多西欧语言版本中的默认编码。
CP1252 把 Latin-1 没有使用的 80 - 9F 使用上了,包含了 Latin-9 新加入的字符。
由于很多采用 CP1252 编码的文件可能声明的编码为 ISO-8859-1,为了避免这些文件在 Web 上显示异常,HTML5 要求把所有声明为 ISO-8859-1 的页面当成是 CP1252 编码处理。

ANSI

ANSI 本意是 American National Standards Institute,美国国家标准协会。

ANSI 作为编码名称的话,全称 ANSI code standard,表示 ANSI 设计的一种 EASCII(据说就是 Latin-1)。

我们看到 ANSI 编码的唯一场景好像就只有 Windows 记事本的 “另存为” 页面的编码选择框。
其中有四个选项,ANSI,Unicode,Unicode big endian,UTF-8。

  1. ANSI 选项实际上是表示操作系统默认编码(外码),中文版本下 ANSI 等于 GBK。
  2. Unicode 选项实际上是 UTF-16 LE
  3. Unicode big endian 选项实际上是 UTF-16 BE
  4. UTF-8 则是带 BOM 的 UTF-8

PS: 这个大端序小端序,以及 BOM,说来话长,本文完全没有提及。

据说 Windows 10 1903 之后,选项变成了 ANSI, UTF-16 LE, UTF-16 BE, UTF-8, UTF-8 with BOM。明显合理多了,只有这个 ANSI 还是狗皮膏药,揭不掉。