regex 总览待审核
番外 · Unicode 之前的中文编码

中文编码:GB2312 / GBK / GB18030 与 Big5

code point 那页讲的是今天怎么把字符变成字节(UTF-8 / UTF-16)。 但在 Unicode 普及之前,中文早已要在计算机里落进字节——靠的是一套套国家 / 地区标准编码: 大陆的 GB2312 → GBK → GB18030(一脉相承、后者是前者的超集), 台 / 港的 Big5(另起一套,收繁体)。它们都把 ASCII 留作单字节、用双字节装汉字 (GB18030 还多出 1 / 2 / 4 字节变长)。下面这个检视器把同一个字在五种编码下的字节并排摊开, 顺带回答:为什么还要发明 GB18030

同一个字,五种编码各自的字节(ASCII · lead 首字节 · 续字节 · ✗ 无此字)

关于 GB2312 这一列:检视器按码位区段判定——某字的 GBK 字节若落在 EUC-CN 区 (首字节 0xA1–0xF7、尾字节 0xA1–0xFE)就算「在 GB2312」。 对汉字这判定是精确的(GBK 没在汉字区动过 GB2312 的码位); 但 GB2312 的符号区原本留有空位,GBK 往里补过个别字符(如欧元 是 GBK 才加的,严格不属 1980 年的 GB 2312–80)。另外现代浏览器 / 标准里 gb2312 这个 charset 标签实际走 GBK 解码器, 解码层面二者已不区分。

双字节怎么排:GB2312 的区位码

GB2312 把要收的字符排进一张 94 区 × 94 位 的方表(区位码):区 01–09 放标点 / 数字 / 字母等图形符号, 区 16–55 放 3755 个一级汉字(按拼音排),区 56–87 放 3008 个二级汉字(按部首排)。 存进机器时按 EUC-CN 规则,把区号、位号各加 0xA0 得到两个字节——加 0xA0 是为了让两个字节的最高位都为 1,这样它们绝不会和 0x00–0x7F 的单字节 ASCII 撞上, 一段混排文本里「这是汉字的字节还是 ASCII」一眼可分。上面检视器对落在 GB2312 区的字会现场拆出它的区 / 位。

谁是谁的超集:四种编码的字节范围

把四套编码的字节布局并排,就能看清「GB2312 ⊂ GBK ⊂ GB18030」这条线,以及 Big5 为何与它们互不相通:

编码首字节后续字节字节数收录
ASCII00–7F1四套编码都把它留作单字节,完全兼容
GB2312 · 1980A1–F7A1–FE2简体 6763 汉字 + 682 符号
GBK · 199581–FE40–FE(除 7F)2全 20902 个 CJK(简 + 繁),GB2312 的超集
GB18030 · 2000+81–FE2 字节同 GBK;4 字节:30–39 / 81–FE / 30–391 / 2 / 4全部 Unicode,GBK 的超集
Big5 · 198481–FE40–7EA1–FE2繁体 13053 汉字 + 符号(台 / 港),另起一套
GBK 打开了「低字节坑」。GB2312 两个字节都 ≥ 0xA1;GBK 为了塞下两万多字,把尾字节范围下探到 0x40–0x7E——这一段和 ASCII 的字母 / 符号重叠。于是一个 GBK 汉字的第二个字节可能正好是 0x5C(\)、0x46(F)这类。把字节当 ASCII 逐个处理(比如按 \ 转义、按 路径分隔符切分)的程序,就会把半个汉字误读成一个 ASCII 字符,典型的「\ 吃字」乱码。 试试在检视器里输入「镕」(朱镕基的镕,GBK 才有的字),看它的第二个字节。

为什么要发明 GB18030

GBK 已经收了两万多汉字,看着够用,但它把容量用尽在双字节的 BMP 汉字上了,装不下两类东西: 其一,Unicode 后来在补充平面追加的大批 CJK 扩展字(扩展 B 及以后,> U+FFFF)与emoji; 其二,中国境内的少数民族文字(藏文、蒙古文、维吾尔文、彝文……)以及 Unicode 里其余所有文字。 需要一套编码同时满足两个看似冲突的要求:

UTF-8 满足第二点,却不满足第一点(它的汉字字节和 GBK 完全不同)。GB18030 的解法是: ASCII 仍 1 字节、原 GBK 字符仍用那 2 个字节,凡是这两者装不下的,一律用 4 字节。 4 字节区与 Unicode 码空间一一映射,所以它本质是「Unicode 的一种中文味变换格式」(类似 UTF,但字节兼容 GBK)。 再加上它是强制性国家标准(在中国销售的操作系统 / 软件必须支持),GB18030 就成了既背得动历史包袱、又面向全 Unicode 的那个答案。

4 字节怎么认。GB18030 的 4 字节序列长这样:[81–FE] [30–39] [81–FE] [30–39]—— 第 2、4 个字节恒落在 0x30–0x39(正好是数字 0–9 的字节)。补充平面字符 (U+10000–U+10FFFF)更是线性映射:U+10000 = 90 30 81 30,一路连续到 U+10FFFF = E3 32 9A 35。在上面检视器里输入「😀」或「㐀」(CJK 扩展 A),会现场拆出这 4 个字节。

跨编码乱码:同一串字节,换一套表就读串

GB 系和 Big5 是两套互不相通的码表:同样的字节,按 GBK 解是一个意思,按 Big5 解就是另一批字。 一段文本若丢失了「我是什么编码」的声明,收方只能猜——猜错就是满屏乱码。 这正是两岸三地早年传文件最常见的麻烦,也是 <meta charset> / HTTP Content-Type 必须带 charset 的原因。

这段文本的 GBK 字节流

同一串字节,两种解码

一条时间线

结局:世界收敛到 UTF-8。编码各自为政的代价就是上面那种乱码,以及每个程序都得猜 charset。 Unicode 给了所有字符统一的 code point,UTF-8 给了统一的字节方案且向下兼容 ASCII,于是网页、源码、协议几乎全面转向 UTF-8。 GB18030 仍在(强制国标、政务系统、需要和 GBK 老数据兼容的场景),但它和 UTF-8 一样,本质都是 Unicode 的一种字节表示—— 回到 code point 那页:逻辑层永远是那个 U+XXXX,变的只是存储层怎么拆字节。 延伸阅读: WHATWG Encoding Standard 定义了浏览器实际使用的 gbk / gb18030 / big5 解码表与 gb2312 → gbk 等标签别名(本页的字节就是用它规定的 TextDecoder 反查出来的); 维基词条 GB 18030Big5 有完整的码表布局与版本沿革。