中文编码:GB2312 / GBK / GB18030 与 Big5
code point 那页讲的是今天怎么把字符变成字节(UTF-8 / UTF-16)。 但在 Unicode 普及之前,中文早已要在计算机里落进字节——靠的是一套套国家 / 地区标准编码: 大陆的 GB2312 → GBK → GB18030(一脉相承、后者是前者的超集), 台 / 港的 Big5(另起一套,收繁体)。它们都把 ASCII 留作单字节、用双字节装汉字 (GB18030 还多出 1 / 2 / 4 字节变长)。下面这个检视器把同一个字在五种编码下的字节并排摊开, 顺带回答:为什么还要发明 GB18030。
同一个字,五种编码各自的字节(ASCII · lead 首字节 · 续字节 · ✗ 无此字)
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 为何与它们互不相通:
| 编码 | 首字节 | 后续字节 | 字节数 | 收录 |
|---|---|---|---|---|
| ASCII | 00–7F | — | 1 | 四套编码都把它留作单字节,完全兼容 |
| GB2312 · 1980 | A1–F7 | A1–FE | 2 | 简体 6763 汉字 + 682 符号 |
| GBK · 1995 | 81–FE | 40–FE(除 7F) | 2 | 全 20902 个 CJK(简 + 繁),GB2312 的超集 |
| GB18030 · 2000+ | 81–FE | 2 字节同 GBK;4 字节:30–39 / 81–FE / 30–39 | 1 / 2 / 4 | 全部 Unicode,GBK 的超集 |
| Big5 · 1984 | 81–FE | 40–7E 与 A1–FE | 2 | 繁体 13053 汉字 + 符号(台 / 港),另起一套 |
0xA1;GBK 为了塞下两万多字,把尾字节范围下探到
0x40–0x7E——这一段和 ASCII 的字母 / 符号重叠。于是一个 GBK 汉字的第二个字节可能正好是
0x5C(\)、0x46(F)这类。把字节当 ASCII 逐个处理(比如按 \ 转义、按
路径分隔符切分)的程序,就会把半个汉字误读成一个 ASCII 字符,典型的「\ 吃字」乱码。
试试在检视器里输入「镕」(朱镕基的镕,GBK 才有的字),看它的第二个字节。
为什么要发明 GB18030
GBK 已经收了两万多汉字,看着够用,但它把容量用尽在双字节的 BMP 汉字上了,装不下两类东西:
其一,Unicode 后来在补充平面追加的大批 CJK 扩展字(扩展 B 及以后,> U+FFFF)与emoji;
其二,中国境内的少数民族文字(藏文、蒙古文、维吾尔文、彝文……)以及 Unicode 里其余所有文字。
需要一套编码同时满足两个看似冲突的要求:
- 向后兼容已经铺开的 GBK 数据——老文件、老系统的字节一个都不用改;
- 又能无损往返全部 Unicode 码点(
U+0000 – U+10FFFF),一劳永逸。
UTF-8 满足第二点,却不满足第一点(它的汉字字节和 GBK 完全不同)。GB18030 的解法是: ASCII 仍 1 字节、原 GBK 字符仍用那 2 个字节,凡是这两者装不下的,一律用 4 字节。 4 字节区与 Unicode 码空间一一映射,所以它本质是「Unicode 的一种中文味变换格式」(类似 UTF,但字节兼容 GBK)。 再加上它是强制性国家标准(在中国销售的操作系统 / 软件必须支持),GB18030 就成了既背得动历史包袱、又面向全 Unicode 的那个答案。
[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 字节流
同一串字节,两种解码
一条时间线
U+XXXX,变的只是存储层怎么拆字节。
gbk / gb18030 / big5 解码表与 gb2312 → gbk 等标签别名(本页的字节就是用它规定的 TextDecoder 反查出来的);
维基词条 GB 18030 与
Big5 有完整的码表布局与版本沿革。