首页/开发/正则与编码速查/NFC 归一化与组合字符合并

NFC 归一化与组合字符合并

NFC 是合成式归一化:把基字符与组合标记合并成预组合字符(é 合成一个码点),是 Web 与多数存储场景的推荐形式,能减少长度与比较上的意外差异。

正则与编码速查开发更新于 2026-10-17
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌​​‌​‌‌‌‌​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌‌‌​‌‌​​‌​‌​‌‌‌‌​​​​​‌​‌‌‌‌​‌‌‌​‌​‌​‌‌​‌‌‌​​‌‌​‌​​‌​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​​‌​​​‌‌​​‌​‌​​‌​‌‌​‌​‌‌​‌‌‌​​‌‌​‌‌‌‌​‌‌‌​​‌​​‌‌​‌‌​‌​‌‌​​​​‌​‌‌​‌‌​​​‌‌​‌​​‌​‌‌‌‌​‌​​‌‌​​​​‌​‌‌‌​‌​​​‌‌​‌​​‌​‌‌​‌‌‌‌​‌‌​‌‌‌​​​‌​‌‌​‌​‌‌​‌‌‌​​‌‌​​‌‌​​‌‌​​​‌‌​​‌​‌‌‌​​‌‌​‌​​​​‌‌‌​‌​​​‌‌​‌‌​‌​‌‌​‌‌​​

NFC 做什么

NFC(Normalization Form Canonical Composition)先按规范做分解,再按规范顺序重新组合可合并的部分,得到「尽量合成」的结果。例如拉丁字母 e 加组合尖音符经 NFC 后变成单个预组合字符;汉字与韩文音节也会被组合成编码表里已有的形式。它的价值在于把同一语义的多种码点写法收敛成一种,使字符串比较、长度统计、索引与去重更稳定。Unicode 官方推荐在网络协议与外部存储中使用 NFC。

基本信息

定义规范等价下的合成形式(Canonical Composition)
典型效果基字母与组合标记合并为一个预组合码点
是否改变语义不应改变(规范等价),但会改变码点序列与长度
官方建议Web 与协议场景推荐使用 NFC
不涉及兼容等价(全角/半角、连字等)不会被 NFC 处理
常用接口Python unicodedata.normalize('NFC', s)、多数语言的 normalize

典型使用场景

  • 用户输入归一:搜索、去重、用户名比对前统一成 NFC,避免同形不同码点
  • 数据入库:存储前归一化,减少同一内容出现多种字节形式
  • 索引与搜索:归一化后建立索引,提升召回一致性
  • 跨系统交换:与外部系统交互前统一形式,减少对方环境的差异
  • 长度校验:归一化后再统计长度,避免「看起来一样却长度不同」

归一化示例

归一化示例
python -c "import unicodedata as u; s='e\u0301'; print(len(s), [hex(ord(c)) for c in s])"        # 2 个码点
python -c "import unicodedata as u; s=u.normalize('NFC','e\u0301'); print(len(s), [hex(ord(c)) for c in s])"  # 1 个码点
python -c "import unicodedata as u; print(u.normalize('NFC','e\u0301') == u.normalize('NFD','\u00e9'))"   # False:形式不同
python -c "import unicodedata as u; a=u.normalize('NFC','e\u0301'); b=u.normalize('NFC','\u00e9'); print(a==b)"  # True:同形式后相等
# 比较前统一形式是标准做法:先 normalize 再比较或建索引

NFC 的表现与影响

对象归一化前归一化后
拉丁重音字母基字母 + 组合标记(2 码点)预组合字符(1 码点)
韩文音节字母序列组合成音节码点
码点总数可能更多通常更少
字节长度随码点变化通常变短
语义不变不变(规范等价)
NFC 只处理规范等价,不处理兼容等价:全角字符、连字、上标数字这类「兼容形式」不会被 NFC 变成普通形式,需要 NFKC。做检索或校验时要想清楚是否需要兼容形式归一。
归一化不是安全措施的反面也不是万能解:某些历史漏洞正是利用归一化时机差(先校验后归一化)绕过过滤。安全校验应在归一化之后进行,并保证「校验用的字符串」与「最终使用的字符串」是同一个。
归一化顺序要固定:多个系统间传递内容时,若一方归一化另一方不归一化,就会出现「内容看起来相同但字节不同」的比对失败。约定统一使用 NFC 并在入口处执行,是成本最低的做法。

易错点

  • 比较前不归一化:同形文本因码点序列不同被判为不等,出现无法复现的匹配失败 → 比较与建索引前统一 NFC
  • 把 NFC 当成兼容归一化:全角与连字不会被处理,检索仍有漏召回 → 需要兼容形式时使用 NFKC/NFKD
  • 在安全校验之后才归一化:攻击者利用形式差异绕过黑名单 → 先归一化再校验,并保证校验对象与使用对象一致
  • 混用 NFC 与 NFD 入库:同一内容出现两种形式,唯一约束与去重失效 → 存储前统一形式
  • 以为归一化会改变语义:规范等价不会改变语义,但会改变长度与字节,长度校验需在归一化后执行

数据来源与参考

  • Unicode 官方:Normalization Forms(UAX #15)
  • Unicode 官方 FAQ:Normalization 与推荐形式
  • Python 官方文档:unicodedata.normalize

整理校对:其其网ICT文档中心 · 本页地址:docs.qiqicto.com/regex/unicode-normalization-nfc.html

​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌​​‌​‌‌‌‌​‌‌‌​​‌​​‌‌​​‌​‌​‌‌​​‌‌‌​‌‌​​‌​‌​‌‌‌‌​​​​​‌​‌‌‌‌​‌‌‌​‌​‌​‌‌​‌‌‌​​‌‌​‌​​‌​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​​‌​​​‌‌​​‌​‌​​‌​‌‌​‌​‌‌​‌‌‌​​‌‌​‌‌‌‌​‌‌‌​​‌​​‌‌​‌‌​‌​‌‌​​​​‌​‌‌​‌‌​​​‌‌​‌​​‌​‌‌‌‌​‌​​‌‌​​​​‌​‌‌‌​‌​​​‌‌​‌​​‌​‌‌​‌‌‌‌​‌‌​‌‌‌​​​‌​‌‌​‌​‌‌​‌‌‌​​‌‌​​‌‌​​‌‌​​​‌‌​​‌​‌‌‌​​‌‌​‌​​​​‌‌‌​‌​​​‌‌​‌‌​‌​‌‌​‌‌​​