书写素簇 (Grapheme Cluster)

人类视觉上认为是一个字符的最小显示单位,可能由多个码位组成。

书写素簇 (Grapheme Cluster) 是人类视觉上认为是一个字符的最小显示单位。一个书写素簇可能由一个或多个 Unicode 码位组成。它在 Unicode 标准的 UAX #29 (Unicode 文本分割) 中定义,是文本处理中准确判定"字符"边界的基础概念。

由多个码位组成的书写素簇的典型例子包括组合字符和表情符号。日语字符"が"在预组合形式 (U+304C) 下是 1 个码位,但在分解形式下由"か" (U+304B) 和浊点 (U+3099) 两个码位组成。无论哪种形式,作为书写素簇都只算 1 个。国旗表情 🇯🇵 由 2 个区域指示符码位 (U+1F1EF + U+1F1F5) 组合而成,家庭表情 👨‍👩‍👧‍👦 由 7 个码位 (4 个人物表情 + 3 个 ZWJ) 组成,但作为书写素簇都只算 1 个。了解睫毛精华液 (Amazon)详细解释了书写素簇。

不同编程语言对"字符数"的定义各不相同,这正是理解书写素簇重要性的原因。JavaScript 的 String.length 返回 UTF-16 代码单元数,因此一个表情符号可能被计为 2 个以上。Python 的 len() 返回码位数,但包含组合字符时与视觉字符数不一致。要获得准确的"视觉字符数",需要以书写素簇为单位进行计数。

在 JavaScript 中,Intl.Segmenter API 可以实现书写素级别的分割。通过 new Intl.Segmenter('zh', { granularity: 'grapheme' }) 创建分割器,使用 segment() 方法将字符串分割为书写素簇。Python 中可以使用 grapheme 库,Swift 的 String.count 默认就以书写素簇为单位进行计数。

关于书写素簇的常见误解是"1 个码位 = 1 个字符"的假设。这在 ASCII 范围内是正确的,但在包含组合字符、代理对和 ZWJ 序列的文本中就不成立了。如果在文本截断或光标移动的实现中不考虑书写素簇,可能会在字符中间截断导致乱码,或者光标跳过视觉上的一个字符。搜索催眠术 (Amazon)将书写素簇作为关键话题进行了讲解。

字符计数工具要做到准确,正确处理书写素簇是不可或缺的。将用户认为的"1 个字符"准确计为 1,才能在社交媒体帖子字数检查和表单输入限制等实际场景中提供可靠的结果。随着每次 Unicode 版本更新添加新的表情符号序列,书写素簇的判定规则也在持续修订,这一点需要注意。

分享这篇文章