从原理上来讲,我们的计算机其实只认识数字(要不然为什么叫做计算机),确切的说是 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 个字符,一个字节的八位全部占满。主要是扩充了希腊字母,拉丁字母,符号。
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 的东西,中文一般翻译为代码页。
我也不清楚这套机制,个人理解如下:
- 早期 IBM PC 需要显卡支持对指定字符编码的图形绘制,不同字符集的绘制规则存在不同芯片中,这些芯片就是代码页。
由于是厂商提供的,所以又叫做 OEM 代码页。 - 图形化之后,操作系统提供字符绘制功能。但是在 Unicode 没有应用到操作系统设计之前,操作系统的内部编码在同一时间只能选择一种编码。
这时代码页叫做 Windows 代码页,或者 ANSI 代码页。 - 后来 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 国家标准,是一种变长多字节编码方案,每个字符由一个、两个、或四个字节组成。
其对 GB 2312-1980 完全向后兼容,与 GBK 基本向后兼容,并支持 Unicode(GB 13000)的所有码位。GB 18030 共收录汉字 70,244 个。
GB 18030 主要有以下特点:
- 采用变长多字节编码,每个字可以由 1 个、2 个或 4 个字节组成。
- 编码空间庞大,最多可定义 161 万个字符。
- 完全支持 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。
ANSI
选项实际上是表示操作系统默认编码(外码),中文版本下 ANSI 等于 GBK。Unicode
选项实际上是 UTF-16 LEUnicode big endian
选项实际上是 UTF-16 BEUTF-8
则是带 BOM 的 UTF-8
PS: 这个大端序小端序,以及 BOM,说来话长,本文完全没有提及。
据说 Windows 10 1903 之后,选项变成了 ANSI
, UTF-16 LE
, UTF-16 BE
, UTF-8
, UTF-8 with BOM
。明显合理多了,只有这个 ANSI 还是狗皮膏药,揭不掉。