Unicode 规范化工具

同一个可见字符串,可能因为重音是作为单一代码点存在,还是作为一个字母加一个组合标记,而被存储为不同的字节序列。这个规范化工具在四种 Unicode 规范化形式(NFC、NFD、NFKC、NFKD)之间转换文本,让你的字符串在数据库、搜索索引、去重脚本和正则匹配中都能干净地比较。

如何规范化 Unicode 文本

  1. 1

    粘贴你的输入

    放入字符串:带重音的字母、CJK 文字、连字,任何让你觉得不一致的内容。

  2. 2

    选择一种形式

    选择 NFC(组合,常用的网页形式)、NFD(分解)、NFKC(兼容组合)或 NFKD(兼容分解)。

  3. 3

    比较计数

    工具会报告前后的字符数(代码点数)和字节长度,让你看出这种形式是让字符串变短还是变长了。

  4. 4

    复制规范化后的输出

    把结果用于你的数据库、API 负载、slug 生成器或测试夹具。

四种形式,以及各自的适用场景

Unicode 规范化由标准附件 UAX #15 定义。四种形式沿两个维度区分:规范与兼容,以及组合与分解。

并列参考

形式 组合 映射 何时使用
NFC 组合 仅规范 网页内容、数据库、文件名的默认选择
NFD 分解 仅规范 按字符去除重音的文本处理
NFKC 组合 含兼容 搜索、标识符、垃圾邮件过滤、显示折叠
NFKD 分解 含兼容 去除变音符号前的激进规范化

规范形式保留含义

输入 é 再切换形式。NFC 报告 1 个字符、2 个字节(单一代码点 U+00E9),而 NFD 报告 2 个字符、3 个字节(一个普通的 e 后面跟一个组合用的锐音符 U+0301)。两者在屏幕上看起来完全一样,却是不同的字节序列,而规范化要解决的正是这种不一致。规范形式只是重新排列字节,所以无论哪种形式,é 始终表示带锐音符的字母 e。

“兼容性”究竟改变了什么

K 形式(NFKC、NFKD)还会重写那些外观相关但带有不同格式的字符。这是有损的:你无法还原出原始内容。例如:

  • fi(U+FB01,拉丁小写连字 fi)会变成 fi(两个字母)
  • ①(U+2460,带圈数字 1)会变成 1
  • ㌀(U+3300,CJK 方形“apaato”)会变成 アパート
  • 全角 A(U+FF21)会变成 A

这对搜索和去重很强大,但对排版是破坏性的,所以只在视觉保真度无关紧要时才选用 K 形式。

一条实用规则

  • 把用户内容存储为 NFC。
  • 用 NFC 比较标识符,让写成单一代码点的 café 与写成 e + U+0301 的 café 相匹配;当你还想让 fi 与 fi、全角 A 与 A 也相等时,就升级到 NFKC,正如 UAX #31 的标识符规则所做的那样。
  • 生成去掉重音的 slug 时,先规范化为 NFD,再删除组合标记区块 U+0300 至 U+036F。

常见问题

通常这意味着你的输入本来就已经是目标形式。粘贴混合了多种来源的文本(Mac 文件名、复制的 PDF 片段、旧数据库中的一行),你就更有可能看到字符数和字节数发生变化。

显示用的 URL 用 NFC。对于想要纯 ASCII 的 slug,先规范化为 NFD,用针对 U+0300 至 U+036F 的正则去掉组合标记,再转成小写。如果你还想折叠连字和带圈数字,NFKD 是一种备选。

规范形式(NFC、NFD)从不改变含义,只是重新排列字节。兼容形式(NFKC、NFKD)则会改变含义:连字被拆开,全角字母被折叠,上标被压平。只在你确实需要时才使用它们。

规范化在我们的服务器上通过 PHP 的 Normalizer 类完成,结果在同一个请求中返回;你的文本不会被保存。我们只记录一条匿名事件,注明应用了哪种形式,绝不记录内容本身。

相关工具

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