做接口限流或者数据库字段设计时,经常要回答一个问题:这段文本占多少字节?英文好估算,一个字母一字节;中文就含糊了,有人说是两字节,有人说是三字节。要弄清楚这个问题,需要从 ASCII 空出来的那半个码表说起,再顺着 UTF-8 的编码规则一路算下去。
ASCII 空出来的第 8 位
ASCII 只用 7 位,也就是 0 到 127 这 128 个位置,第 8 位原本留给奇偶校验。这意味着 128 到 255 这另外 128 个位置是空着的。
在只有英文的世界里,空着没问题。但当计算机开始在欧洲普及,法语、德语、西班牙语里的带音标字母就没地方放了。最直接的解决方案是把空着的第 8 位用起来,于是 ISO-8859-1 出现了,它把 128 到 255 填上西欧语言所需的字符,习惯上称为 Latin-1。
需要留意一个细节:128 到 159 这 32 个位置在 Latin-1 里是 C1 控制字符,不可打印,日常很少用到;真正承载西欧字母和符号的是 160 到 255 这一段。常见的内容包括带音标的字母(à á â ä ç é è ñ ö ü)和一批数学、货币、排版符号(© ® ° ± × ÷ ¼ ½ ¾)。
一个编码,多种扩展
Latin-1 的思路被广泛复制,很快就出现了 ISO-8859 系列的其他成员:Latin-2 用于中欧语言,Latin-5 用于土耳其语,Latin-7 用于波罗的海语系。它们都占用 128 到 255 这段空间,但填的内容不同。
问题也随之而来:同样一个字节值,在 Latin-1 里是 é,在 Latin-2 里可能是别的字符。更麻烦的是中文场景——GBK、GB2312 也把汉字放在这段空间里,而且用的是双字节方案。于是同一个字节序列,用不同编码解读会得到完全不同的文本,这就是上一代人经历过的「编码猜谜」时代。
UTF-8 的出现终结了这场混乱:它用变长编码把全世界的字符统一到一个体系里,同时保持与 ASCII 的兼容。
UTF-8 的字节数规则
UTF-8 的编码规则可以按码位范围归纳成一张表:
| 码位范围 | 字节数 | 典型内容 |
|---|---|---|
| U+0000 — U+007F | 1 | ASCII 字符 |
| U+0080 — U+07FF | 2 | Latin-1 扩展、希腊字母、西里尔字母 |
| U+0800 — U+FFFF | 3 | 常用汉字、日文假名、韩文 |
| U+10000 — U+10FFFF | 4 | emoji、生僻汉字、古文字 |
这张表解释了两个常见疑问。第一个:为什么英文文本的字节数等于字符数?因为 ASCII 范围内的字符在 UTF-8 下就是一个字节。第二个:为什么中文文本的字节数约等于字符数的三倍?因为常用汉字落在 U+0800 到 U+FFFF 这一段,每个占三个字节。
还要注意一个容易忽略的点:Latin-1 里的字符在 UTF-8 下占两个字节,不是三个。带音标的 é 属于 U+00E9,落在两字节区间。所以在做体积估算时,「非 ASCII 字符都是三字节」这个印象是错的,要看具体码位。
几个估算实例
把这套规则套到具体场景,估算就变得简单了。
纯英文的 100 个字符,无论字母、数字还是标点,只要在 ASCII 范围内,就是 100 字节。
纯中文的 100 个字,按每个三字节计算,约 300 字节。这也是为什么数据库字段按字节长度定义时,中文内容的可用字数只有名义值的三分之一左右。
一个 emoji 占四个字节。如果一条评论里出现了十个 emoji,仅这部分就是 40 字节。emoji 是导致「字段明明够长却写不进去」的高频原因。
中英混排时,把中文按三倍、英文按一倍分别算再相加,误差通常在可接受范围内。如果文本里还有 Latin-1 的带音标字母,按两倍计算。
为什么数据库要用 utf8mb4
最后回到一个实践问题:为什么 MySQL 里推荐用 utf8mb4 而不是 utf8?
原因在于 MySQL 的 utf8 实际上是一个不完整的实现,每个字符最多只用三个字节。这刚好能覆盖常用汉字,但放不下四字节的字符——也就是 emoji 和一部分生僻字。往一个 utf8 字段里写入 emoji,会被截断或直接报错。
utf8mb4 支持完整的四个字节,是真正符合 UTF-8 规范的那一个。新项目建库建表时直接指定 utf8mb4 和对应的排序规则,可以省掉后续大量的兼容麻烦。
检查清单
- ASCII 只用 7 位,128 到 255 是 Latin-1 等扩展编码的空间
- Latin-1 里 128 到 159 是控制字符,160 到 255 才是可打印的西文字符
- UTF-8 按码位范围决定字节数:ASCII 一字节、Latin-1 两字节、常用汉字三字节、emoji 四字节
- 估算体积时英文按一倍、带音标字母按两倍、中文按三倍,再相加
- 数据库统一使用
utf8mb4,避免四字节字符写入失败 - 字段按字节长度设计时,中文内容的可用字数约为名义值的三分之一