NFD 做什么
NFD(Normalization Form Canonical Decomposition)按规范分解规则把字符拆开:预组合的重音字母变成基字母加组合标记,韩文音节拆成字母序列。它不重新组合,因此得到的是「尽量分解」的形式。用途集中在需要按键位或基础字符做匹配的场景,例如忽略重音的搜索、按基字母建索引、字体与渲染管线处理组合标记。代价是码点数变多、字节变长,且与 NFC 内容不相等。
基本信息
| 定义 | 规范等价下的分解形式(Canonical Decomposition) |
|---|---|
| 典型效果 | 预组合字符拆成基字符加组合标记 |
| 码点变化 | 通常变多,长度与字节数增加 |
| 是否改变语义 | 规范等价,语义不变 |
| 典型用途 | 忽略重音的检索、基字符索引、渲染处理 |
| 注意点 | 与 NFC 结果不相等,混用会导致比较失败 |
典型使用场景
- 忽略重音的搜索:把两侧文本都分解并去掉组合标记后比较
- 排序与分组:按基字符归组,把 é 与 e 视为同一组
- 字体与排版:文本渲染引擎常按分解形式处理标记定位
- 数据清洗:把形态不统一的输入拆开再统一重组,便于后续处理
- 字符统计:区分基字符与组合标记,统计实际内容构成
分解与去标记示例
分解与去标记示例
python -c "import unicodedata as u; s=u.normalize('NFD','\u00e9'); print(len(s), [hex(ord(c)) for c in s])" # 2 个码点:e + 组合尖音符
python -c "import unicodedata as u; print(u.normalize('NFD','\u00e9') == '\u00e9')" # False:形式不同
python -c "import unicodedata as u; s=u.normalize('NFD','caf\u00e9'); print(''.join(c for c in s if u.category(c)!='Mn'))" # 去掉组合标记得到 cafe
# 常见检索流程:两侧都 NFD 再去标记 → 比较;展示时用 NFC
python -c "import unicodedata as u; print(u.normalize('NFC', u.normalize('NFD','caf\u00e9')))" # 可逆回到 NFCNFC 与 NFD 对比
| 维度 | NFC | NFD |
|---|---|---|
| 方向 | 合成 | 分解 |
| 重音字母 | 单码点 | 基字符加组合标记 |
| 码点数量 | 通常较少 | 通常较多 |
| 字节长度 | 通常较短 | 通常较长 |
| 推荐用途 | 存储、交换、比较 | 检索、索引、渲染处理 |
| 两者相等性 | 互相不相等 | 互相不相等 |
NFD 不等于「去掉重音」:它只是把字符拆开,组合标记仍在字符串里。要得到无重音文本,需要在分解后按通用类别过滤掉标记字符,这一步属于业务逻辑,不同语言的实现方式不同。
归一化可逆性有前提:从 NFC 到 NFD 再回到 NFC 通常能还原,但经过兼容归一化(NFKC/NFKD)或自定义过滤后就不再可逆,涉及存储与检索时要把两种形式分别处理。
排序与比较的正确做法是使用语言相关的排序算法(collation),而不是自己用 NFD 拼凑:Unicode 提供了排序算法标准,能处理重音、大小写、区域差异等规则,自研规则很难覆盖全语言。