← 实验室
ASTERXOR / 实验室

为什么一个“字”,可能不止一个单位

可见符号、Unicode码点和存储字节不是同一种单位,数文字前要先选尺子。

独立金色标记和一个小蓝色部件组合成相连符号。
多个存储部件组成一个显示单位的概念图。
实验室ASTERXOR / LEON LEUNG

看起来一样的重音字母,长度可能不同。

é可以用一个预组合码点表示,也可以由普通e加组合尖音符组成。两种序列可能显示相同,Python却分别数出长度1和2。这不是数错了,而是程序使用的单位不总等于读者心中的“一个字符”。

数文字之前,先说清单位。
THREE RULERS

字节、码点和可见单位

字节是八位的存储单位;Unicode码点是字符编码空间里的编号;扩展字素簇则是标准分段规则对用户感知字符的近似。渲染还会加入字体、塑形和呈现方式。各层相互配合,但计数未必相同。

Python字符串是码点序列,因此len计数的是码点。编码成UTF-8后,又能单独计算字节。实验对六个固定例子执行两种计数,没有运行字素分段器,也没测试所有字体是否把每例显示成一个符号。

READ THE NUMBERS

一个不一定是一字节

A有一个码点和一个UTF-8字节;预组合é有一个码点、两个字节;分解的e加音符有两个码点、三个字节;韩文音节한在本例有一个码点、三个字节。因此,Python字符串长度不能单独决定存储开销。

带肤色的点赞👍🏽有两个码点、八个字节;女程序员👩‍💻有三个码点、十一个字节,其中包含零宽连接符。支持该序列的渲染器可能把它们合成一个视觉符号。没有单独印出来的部件,也有实际意义。

字母和表情序列的码点数与字节数并列比较。
FIG 01六个固定字符串的实际len与UTF-8字节数。英文标签避免依赖表情字体。
CANONICAL FORMS

两种写法可以归到同一形式

规范化前,两种é在Python中不相等,因为码点序列不同。应用NFC后,本例两者变成相同的预组合形式。脚本确实验证了这个结果,而不是把外观相似直接当作逐字节相同。

规范化不等于随意抹平所有区别。NFC处理规范等价;兼容规范化还会做额外改变,后果不同,两者都不是通用的形似名字检测器。系统应选择适合目的的明确策略,原始形式重要时还要保留输入。

规范化前分别一、两个码点,之后都为一个。
FIG 02NFC将这两个规范等价的样例映射为同一预组合表示。
THE CURSOR

切开存储,也可能切碎视觉符号

字节上限、码点上限和显示字符上限可能停在不同位置。在多字节UTF-8序列中间截断,会留下无效字节;在e与音符之间或表情序列内部切开,码点仍可能有效,却破坏读者预期的符号。

因此截断、光标移动和字数限制需要明确规则。Unicode字素边界提供共同基础,语言和产品情境有时还需补充处理。这六个例子解释为什么需要这一层,不是完整分段测试,也不保证所有编辑器表现一致。

AT THE BOUNDARY

长度限制也是接口承诺

若表单说最多二十个字符,数据库却只许二十个字节,纯ASCII输入可能掩盖矛盾,重音字母和表情则很快暴露它。用户遵守界面规则,仍可能被存储层拒绝。问题是约定不一致,而非文字特别难伺候。

清楚的系统会指定单位,明确编码解码,并测试有代表性的文字和组合,不只测试英文字母。搜索与比较也要谨慎处理相似外观。第一步很简单:把“字符串多长”改问成字节、码点,或人预期怎样编辑的单位有多少。

留一个想法带走

每种长度都带上自己的尺子,文字就没那么神秘。