跳到主要内容

Unicode 规范化全解:NFC、NFD、NFKC、NFKD 怎么选

同一个 é 可以是一个码点也可以是两个,看着一样却不相等。本文讲清 NFC、NFD、NFKC、NFKD 四种 Unicode 规范化形式的区别,以及搜索、去重、文件名一致和全角半角统一里该怎么用。

发布于 作者 李雷
#Unicode #文本处理 #编码 #开发工具

Unicode 规范化:为什么看着一样的字符串会不相等

我第一次被这个坑绊倒,是排查一条"明明存在却搜不到"的人名记录。数据库里清清楚楚写着 José,搜索框里也敲的是 José,屏幕上两个字一模一样,查询结果却是空的。折腾半天才发现:存进去的那个 é 是两个码点,搜索发来的那个是一个码点。它们渲染出来分毫不差,作为字符串却不相等。这就是 Unicode 规范化要解决的问题。

同一个字形,两种存法

Unicode 为了和旧字符集双向兼容,给 é 保留了一个预合成码点 U+00E9。同时它又允许你用基字母 e(U+0065)加上一个组合用的尖音符 U+0301 拼出同一个字形。两种写法都合法,屏幕上看着完全一样,但按字节去比就是两回事:前者是一个码点、两个 UTF-8 字节;后者是两个码点、三个字节。

所以当你在 JavaScript 里写 "é" === "é",结果可能是 false。不是引擎出错,是这两个 é 的底层码点序列根本不同。规范化的作用,就是把这种"看着一样"的多种写法,收拢成一个统一的标准形状,让比较结果和肉眼一致。

四种形式:合成、分解、加不加兼容折叠

规范化有四种形式,可以拆成两组维度来理解。

第一组是合成还是分解。NFC(Normalization Form C,Canonical Composition)倾向合成:在有单一码点可用时,把基字母和它的音符打包成一个码点,é 收成 U+00E9。NFD(Canonical Decomposition)倾向分解:把同一个 é 拆成 e 加组合尖音符,变成两个码点。这两种都是标准形式,可逆,含义不变。存储和比较一般选 NFC,它也是网页的默认;要去掉音符或做某些排序时,NFD 更顺手,因为你可以直接剥掉那些组合标记。

第二组是加不加兼容折叠。NFKC 和 NFKD 在前两者基础上多了一层,把"只是格式不同"的变体压回普通写法。全角 ABC 折成半角 ABC,带圈 ① 变成数字 1,fi 连字拆成 fi 两个字母,上标和不少符号变体也归到基字符。这一层是有意会丢信息的:你用外观的精确,换一个可搜索的统一形状。所以搜索引擎和用户名校验,通常在比较前先折成 NFKC。

一个真实的输入输出例子

拿前面那个 é 走一遍,差别看得最清楚。

输入预合成的 é(NFC 形式),码点序列是:

U+00E9          → 1 个码点,2 个 UTF-8 字节

切到 NFD 分解,同一个 é 变成:

U+0065 U+0301   → 2 个码点,3 个 UTF-8 字节

字形没动,码点数从 1 跳到 2,字节数从 2 变到 3。那个凭空冒出来的 U+0301,就是组合用的尖音符。把这两段分别规范成 NFC,它们就会重新对齐,比较结果也就相等了。你可以在 Unicode 规范化工具 里亲手试:粘进文本,切换四种形式,它会显示规范化前后的码点数和字节数,还能逐字符列出每个码点、把变了的高亮出来。

搜索、去重和文件名,都要先规范化

这件事的价值,在数据量上来之后才显出来。

搜索匹配:如果一条记录把 José 存成 NFC、另一条存成 NFD,普通的字符串相等判断会把它们当成两个人。先把两边都规范到同一形式,看着一样的就能匹配上。

列表去重:从两个来源合来的 CSV 里,Café 和 Café 看着一行其实是两行,去重会把两条都留下。把这一列过一遍 NFC(若还出现全角或连字变体就用 NFKC),视觉相等的条目就变成字节相等,合并成一条。

文件名一致:macOS 历史上倾向用分解形式存文件名,很多其他系统用合成形式。跨平台同步时,同一个带音符的文件名可能被当成两个文件。统一规范化能消掉这种幽灵差异。

要记住的一条原则是:规范化在写入时做一次,别在每次读取时反复做。

全角半角统一,交给 NFKC

中文场景里最常用到的,其实是 NFKC 这层兼容折叠。用户提交的文本经常夹着全角 ABC、带圈 ①②③、从 PDF 粘来的 fi fl 连字。这些都不该改变搜索的匹配结果。NFKC 一遍就把它们折成普通的 ABC、123、fi fl,你的索引和相等判断看到的,就是人读到的那些字符。

但要小心两个边界。一是不要在只需要 NFC 的地方用 NFKC,后者会把全角、连字、带圈数字永久折掉,展示文本和存档原文该用可逆的 NFC,NFKC 只留给搜索键。二是别以为 CJK 完全不动:中文表意字没有标准分解,但 NFKC 仍会折叠混排文本里的全角拉丁字母和半角假名,所以一段看着全是中文的字符串照样可能变。下结论前,先看一眼字节数变化。

排查这类问题时,搭配 Unicode 字符审查器 可以逐个码点看清每个字符的真实身份,两个工具一起用,基本能把"看着一样却不相等"的怪事彻底定位清楚。


Made by Toolora · Updated 2026-06-13