字符串长度计算器

粘贴字符串后,该工具会从四个不同维度显示其长度:JavaScript 风格的 .length(UTF-16 编码单元)、Unicode 代码点、字素簇(用户感知为单个字符)以及 UTF-8 字节(数据库列实际存储的内容)。对于包含表情符号、旗帜或组合标记的字符串,这些数值会互不相同,而这种差异正是诸多 bug 的根源。

字符串长度的四种含义

  1. 1

    UTF-16 代码单元

    `str.length` 在 JavaScript 中返回的值:大多数字符返回 1,超出 U+FFFF 范围的代码点(如表情符号、古文字)则返回 2。

  2. 2

    代码点

    每个 Unicode 标量对应一个。表情符号各占 1 个;而 ZWJ 序列则包含多个。

  3. 3

    字素簇

    用户所称的“一个字符”。`🇺🇸` 包含 2 个代码点,但对应 1 个字素;`n̈` 同样包含 2 个代码点,但对应 1 个字素。

  4. 4

    UTF-8 字节

    数据库存储的内容:ASCII 字符各占 1 字节;常用欧洲字符占 2 字节;CJK 占 3 字节;大多数表情符号占 4 字节。

示例

字符串 JS .length 代码点 字素 UTF-8 字节
hello 5 5 5 5
café 4 4 4 5
😀 2 1 1 4
🇺🇸 4 2 1 8
👨‍👩‍👧(家庭) 8 5 1 18
n̈(n + 分音符) 2 2 1 3

为什么数字会不一样

JavaScript 字符串采用 UTF-16 编码,因此超出 U+FFFF 范围的代码点会以代理对形式存储,即两个 UTF-16 代码单元,例如 "😀".length === 2。用户对此颇有微词,因为他们把 😀 看作一个独立字符。

字素的定义则更进一步:国旗实际上由两个表示地区标识的代码点组合而成,但用户会将其视为单一旗帜。例如 JavaScript 中的 "🇺🇸".length === 4,听起来有些荒谬,但事实如此。若需遍历字素,可使用 Intl.Segmenter(现代方案)、grapheme-splitter 库,或手动处理。

UTF-8 字节:你的数据库实际存储的内容

对于类型为 VARCHAR(255) 的列:

  • 在 MySQL utf8(实际为 utf8mb3)中,255 指的是字节数;而 café 占用 5 字节,因此可存储 51 个副本。
  • 在 MySQL utf8mb4(真正的 UTF-8,包含表情符号)中,仍然以字节计数,但其编码支持 4 字节序列。
  • 在 Postgres VARCHAR(255) 中,255 表示字符数(代码点),而非字节。
  • 在 SQL Server VARCHAR 中,计数方式因排序规则(collation)而异;NVARCHAR 则按 2 字节的 UCS-2 单元计数。

若按用户可见的字符上限来设置数据库字段大小,在 UTF-8 列中建议使用“字节数 × 4”作为安全系数,每个字素的每个代码点最多可占用 4 字节,而 ZWJ 序列还会占用更多空间。

Twitter 风格的字符计数

Twitter 按自定义规则统计推文:以代码点为单位,但表情符号和 CJK 表意文字各计为 2。纯 ASCII 的推文最多可包含 280 个字符;而使用 140 个表情符号的推文上限仅为 140 个“字符”。

短信字符计数:GSM 7 位编码下上限为 160 个字符。一旦使用非 GSM 字符(如 á、é、ñ 或表情符号),编码就会切换为 UCS-2,消息长度随之降至 70 个字符。

实际应用

  • 数据库列大小设置,在编写迁移脚本前,先检查字节数。
  • API 配额限制,大多数 API 以字节而非字符为计量单位。
  • 表单验证,向用户显示与其预期相符的准确字符数(字素)。
  • 性能调试,这条正则表达式为什么遍历得这么慢?因为输入在代码单元上的长度是字素长度的 3 倍。

常见问题

该表情符号是由多个代码点组成的 ZWJ 序列(例如家庭表情符号由 7 个代码点构成)。Twitter 根据 Unicode 权重规则将其计为 2 个字符,而你的编辑器则显示为 1 个字素。两者在技术上都正确,只是衡量的对象不同。

在 UTF-8 数据库中,为确保安全,建议按每个预期字符 4 字节来预留。包含 100 个字符(字素)的字段应设为 VARCHAR(400) 字节;若数据库像 Postgres 那样按字符计数,则使用 VARCHAR(100) 并配合相应的字符集处理。

在 JavaScript 中,这取决于规范化形式。NFC 组合形式(U+00E1)长度为 1;NFD 分解形式(U+0061 + U+0301)长度为 2。可见字符相同,但字节序列不同。

家庭和职业类表情符号由很长的 ZWJ 序列组成。不完全支持这些序列的字体会把每个代码点分别渲染为独立字形,因此 👨‍💻 会显示为 👨+💻。升级操作系统字体即可解决此问题。

相关工具

此工具还提供其他语言版本