从一个『是不是两个字符占两个字节』的疑问说起
很多新手以为「一个汉字 = 两个字节」,其实这个说法只在某种旧编码下近似成立。真相是:字节数由编码方案决定,不由字符本身决定——同一个『中』字,在 GBK 里占 2 字节,在 UTF-8 里占 3 字节,在 UTF-32 里占 4 字节。要讲清这个,得从编码的演进说起。
第一次跨越:ASCII 与 7 位有限宇宙
1950–60 年代,ASCII 用 7 位定义了 128 个字符:大小写字母、数字、标点与控制符。它简洁、统一,但也只够英语——128 个名额装不下全世界的文字。
'A' = 65 = 0x41 = 1000001 (1 字节,7 位有效)
第二次跨越:扩展编码与乱码的温床
各国想表达自己的文字,就各自定义『0x80 以上的字节是什么』:西欧 ISO-8859-1、中文 GBK/GB2312、日文 Shift-JIS… 麻烦在于互不兼容——同一字节 0xE4 B8 AD(UTF-8 的『中』),按 GBK 读就成了别的字。乱码的本质 = 写入编码与读取编码不一致。
第三次跨越:Unicode 与 UTF-8 各司其职
- Unicode(字符集):给每个字符一个全局唯一码点。『中』= U+4E2D;
- UTF-8(编码):把这些码点编码成字节。
UTF-8 变长规则
| 首字节 | 字节数 | 有效位数 | 覆盖范围 |
|---|---|---|---|
0xxxxxxx |
1 | 7 | U+0000–007F(ASCII) |
110xxxxx |
2 | 11 | U+0080–07FF |
1110xxxx |
3 | 16 | U+0800–FFFF(含 CJK) |
11110xxx |
4 | 21 | U+10000+(emoji 等) |
后续字节一律 10xxxxxx。解码器读首字节就能确定字长,且纯 ASCII 文本天然合法。
把『中』落到纸上
码点 U+4E2D = 0x4E2D
二进制 0100 1110 0010 1101 (共 16 位 → 需 3 字节)
按 1110xxxx 10xxxxxx 10xxxxxx 展开:
1110 0100 10 111000 10 101101
───────── ──────── ────────
E4 B8 AD
→ UTF-8 字节: 0xE4 0xB8 0xAD
这就是『一个汉字在 UTF-8 里是 3 字节』的来源。
UTF-16 与 HTML 实体:另两条路
- UTF-16:基本平面字符占 2 字节,增补平面用代理对占 4 字节。Windows 与许多系统内部用它,但网络与文件更常见 UTF-8;
- HTML 实体
中:用十六进制码点直接在 HTML 里写字符,不依赖文件编码也能正确显示,适合在受限环境里引用符号。
彻底告别乱码的清单
- [ ] 所有文本文件统一 UTF-8(无 BOM);
- [ ] HTTP 响应头带
Content-Type: text/html; charset=utf-8; - [ ] 数据库建表用
utf8mb4(不是老式 utf8,utf8装不下 emoji); - [ ] 前后端交互约好按 UTF-8 传输、JSON 默认 UTF-8 即可;
- [ ] 遇到乱码时,先确认『这段字节用什么编码写的、用什么读的』,再转换,而不是乱猜。
自查
用文本转 Unicode/Unicode 转义工具,把『中』、一个 emoji、一个 ASCII 字母分别转成 U+xxxx 码点和 UTF-8 字节,逐个验证上面的变长规则。能亲手把码点展开成 UTF-8 字节、并解释『为什么纯 ASCII 是合法 UTF-8』时,你就彻底看懂编码了。