编码与字符集
看起来相同的文字,为什么不相等?
使用工具
外观相同的文本,码点序列可能不同。搜索或去重与肉眼判断不一致时,查看内部往往比反复重输更快。结合 Unicode 信息与规范化工具查找实际差异。
不要把外观相似的内容全合并。姓名、商品名、公式和标识符可能需要保留区别。规范化不是纠错,也不是万能危险字符过滤器,而是制定等价规则的依据。
用两种方式组成 é
é
é第一行是 U+00E9,第二行是 U+0065 后接 U+0301。通常都显示为 é,却分别有一、两个码点。分开检查,避免把换行带入。换字体只改外观,不改序列。
第二行规范化后,NFC 合成为 é,NFD 保留 e 加音符的分解形式。两者都提供确定的比较形式。NFC 结果再次 NFC 应不变。对照前后码点和 UTF-8 字节数可说明变化。
全角 A 与圈号 ① 需要另一种判断
A①NFC 保持原样,NFKC 变成 A1。兼容规范化可帮助搜索匹配,却会丢失圈号和全角区别。可能需要规范化搜索键,同时保留展示原文。
NFKD 也做兼容转换,但使用分解形式。不要从四种结果中选最短的当正确答案。系统需统一形式,并接受它消除的区别。以后改策略还可能影响既有 ID 和搜索行为。
规范化不一定会删掉不可见字符
ab
ab检查可见 a、b 之间有 U+200B,虽无可见宽度,仍是码点。NFC、NFKC 都不会把此例变成 ab。规范化不是删除一切不可见字符的清理,允许字符规则应另行考虑。
分类用于提示空白、组合符与控制格式字符,不是完整名称库或安全判决。零宽字符可能有语言和排版用途,不能一概视为恶意或多余。除问题例,还应测试必须正确支持的文字。
像 a 的字,不一定属于同一文字系统
a
а前者 U+0061,后者 U+0430,字体下可能很像,却属于不同文字系统,NFKC 不会合并。已规范化不等于没有可冒充的形似字,URL 和账号安全仍需另查。
反过来用自制表替换所有形似字,可能破坏其他语言的合法名字。搜索候选的宽松匹配,与登录权限的身份匹配不必用同一规则。这里检查差异,最终策略按服务用途决定。
变化位置不是编辑历史
变化位置按码点数组下标比较。一个字符分解后会使后续位置偏移,可能标出多处差异。这不是编辑次数或错字数,应看完整前后序列和上下文。
UTF-16 位置与码点位置也可能不同,尤其前面有表情时。JavaScript 偏移和码点计数是不同单位。核对报错位置要统一单位以及从 0 还是 1 开始。
先定位原因,再决定转换策略
保留原文,确定不同码点,再区分 NFC 能统一、需要兼容转换、或规范化无效。然后决定哪些可转换、哪些必须保留原样,不要把 NFKC 当万能修复。
记录可见文本和码点序列,把组合与分解 é、全角 A、零宽字和跨文字系统 a 留作测试,同时验证该相同与该不同的情况。可靠匹配不仅修好一例,还要避免误合并。
搜索中视为相同,不等于改写原数据
商品搜索可只规范化比较用字符串,让全角A找到 ASCII A,而不改正式商品名。原表记与搜索表记分开可兼顾可发现性和数据保留。比较 NFC、NFKC 是决定用途允许哪些等价,不是改得少或多就更好。
对已有规则的用户 ID、签名字符串或密码后来加规范化,可能改变兼容性。外观相同不是修改存储数据的依据。先查码点,再看各规范化形式改变哪里,最后核对目标系统比较规则。找到原因与允许迁移数据是不同决定。
将合成与分解的 é、全角与 ASCII A、零宽字符插入分别做小例子,混合原因会难以判断哪步有效。除外观外还看码点和 UTF-8 大小。明确用途需要的有限等价范围,比试图让所有相似字符相同更能避免误匹配。