字符数和字节数为什么差三倍

工具相关 ·

产品文档里写着「标题不超过 200 字」,开发照着建了一个长度 200 的字段,上线当天就收到了报错:用户输入一段 90 个汉字的中文标题,写入失败。双方都很困惑——明明是「200 字」,怎么会不够?问题出在「字」这个字的两种口径上:产品说的是字符数,数据库实际校验的是字节数。

一个数「多少个字」,一个数「占多少空间」

字符数是按 Unicode 码点计数的,一个汉字、一个字母、一个 emoji 都算一个。它回答的是「这段文本有多少个符号」。

字节数是文本按某种编码写出后占用的存储空间。它回答的是「这段文本要占多少地方」。

在 ASCII 时代这两个数字几乎相等,因为一个字符就是一个字节。但 UTF-8 是变长编码,不同字符占用的字节数并不相同:

字符类型UTF-8 字节数举例
英文、数字、半角标点1a 7 ,
带音标字母、希腊字母2é α
常用汉字、日文假名3中 あ
emoji、生僻汉字4😀

所以一篇「800 字」的中文文章,UTF-8 字节数大约是 2400;同样 800 个字符的英文,字节数就是 800。差了整整三倍。

哪些场景看字节数

字节数不是学术概念,它在很多地方直接决定功能能不能用。

最典型的是索引长度限制。老版本 InnoDB 的单列索引前缀限制是 767 字节,在 utf8mb4 下每个字符最多占 4 字节,所以 varchar(255) 建索引时算出 1020 字节,直接超过上限报错。解决办法是缩短索引列长度,或者改用前缀索引 KEY (col(191))——191 这个数字之所以在社区里流传,正是因为它乘 4 之后刚好不超过 767。

第二类是传输层的体积限制。API 网关、反向代理、消息队列通常按字节设上限,请求体一旦超过阈值就被拒绝。日志系统也会按字节截断字段,中文日志因此比英文日志更早被切断。

第三类是计费与配额。短信按编码方式计费,中文用 UCS-2 编码,一条短信只能容纳 70 个字符,而英文用 GSM-7 编码可以放 160 个。同样的文字量,中英文的短信条数差别很大。

哪些场景看字符数

反过来,面向写作和展示的场景通常看字符数。

社交平台的字数限制基本都按字符计,用户在编辑器里看到的计数就是这个口径。搜索引擎对页面描述(meta description)的截断、列表页标题的截断,也都以字符为单位。

这就带来一个容易混淆的局面:同一个字段,前端校验用的是字符数,后端存储考虑的是字节数,两边如果不明确口径,就会出现「前端通过、后端报错」的情况。工程上的做法是在接口文档里把口径写清楚,例如统一写成「最多 200 个字符(UTF-8 编码后不超过 600 字节)」。

怎么快速估算

实际工作中不需要每次都去算编码,记住三条经验值就够了:英文和半角标点按 1 算,带音标的字母按 2 算,汉字按 3 算,emoji 按 4 算。

把文本按这几类分别统计再相加,误差通常可以接受。如果手上正好有一个能同时给出字符数和 UTF-8 字节数的统计工具,直接量一遍最稳妥——尤其是中英混排、夹带 emoji 的文本,手算很容易漏掉细节。

另外要注意换行符。Windows 风格的换行是 CR + LF 两个字节,Unix 风格是一个字节。一篇文章如果有几百行,仅换行符的差异就有几百字节。

检查清单

  • 明确口径:写作场景看字符数,存储与传输场景看字节数
  • 记住 UTF-8 的经验值:英文 1、带音标字母 2、汉字 3、emoji 4
  • 建索引时注意字节上限,utf8mb4 下长字符串列建议用前缀索引
  • 接口文档里同时写明字符数与字节数上限,避免前后端口径不一致
  • 短信、日志截断、请求体限制这类场景按字节核算
  • 中英混排、含 emoji 的文本直接用量具测一遍,不要手算
阅读 10