何时使用Unicode规范化形式NFC和NFD?

33
Unicode Normalization FAQ包含以下段落:
程序应始终将规范等效的Unicode字符串视为相等... Unicode标准提供了可以用于此目的的定义明确的归一化形式:NFC和NFD。
并且继续说道...
使用哪种取决于特定的程序或系统。对于一般文本,NFC是最佳形式,因为它与从旧编码转换的字符串更兼容。... NFD和NFKD最适合内部处理。
我的问题是:
什么使NFC成为“一般文本”的最佳选择?什么定义了“内部处理”,为什么最好留给NFD?最后,不考虑什么是“最佳选择”,只要两个字符串使用相同的归一化形式进行比较,这两种形式是否可以互换?

“NFC是一般文本的最佳形式,因为它与从旧编码转换而来的字符串更兼容。... NFD和NFKD对于内部处理最有用。”这些说法有些虚假。虽然旧字符串可能以NFC形式转换为Unicode,但为了未来的维护(代码总是在意想不到的情况下使用),最好明确地将转换为NF[CD]形式。” - ninjalj
3个回答

12

常见问题解答中,“应该”一词的使用有误导成分,并且对同一事物的“要求”使用不一致。Unicode标准本身(在常见问题解答中引用)更为准确。基本上,您不应该期望程序将规范等效的字符串视为不同,但也不应该期望所有程序将它们视为相同。

实际上,这取决于您的软件需要做什么。在大多数情况下,您根本不需要进行归一化,而且归一化可能会破坏数据中的关键信息。

例如,U+0387希腊字母ANO TELEIA(·)被定义为与U+00B7中点(·)规范等效。这是一个错误,因为这些字符实际上是不同的,并且应该以不同的方式呈现和处理。但由于Unicode的某个部分已经固定下来,因此现在无法更改。因此,如果您将数据转换为NFC或以其他方式丢弃规范等效字符串之间的差异,则存在获取错误字符的风险。

不进行归一化也存在风险。例如,字母“ä”可以出现为单个Unicode字符U+00E4 LATIN SMALL LETTER A WITH DIAERESIS或两个Unicode字符U+0061 LATIN SMALL LETTER A U+0308 COMBINING DIAERESIS。它通常是前者,即预组合形式,但如果是后者,并且您的代码测试包含“ä”的数据时仅使用预组合形式,则无法检测到后者。但在许多情况下,您只需存储数据、连接字符串、打印它们等。然后存在这样一种风险,即两个表示方法会导致略有不同的呈现。

此外,您的软件是否以某种方式将字符数据传递给其他软件也很重要。由于天真的隐含假设或有意识并以文档方式,接收方可能期望其输入已经归一化。


1
一个需要使用U+0061 LATIN SMALL LETTER A U+0308 COMBINING DIAERESIS来表示“ä”的地方是Max OS X文件名,这需要特定版本的NFD。 - hippietrail
@hippietrail 这个有文档记录吗? - Keith4G
1
@Keith4G:在stackoverflow上应该有相关问题。让我帮你找一下。我不是苹果电脑的专家,但很多年前曾经为了好玩儿做过一些读取苹果电脑分区的事情,并且遇到了这个问题。 - hippietrail
2
技术笔记TN1150 / HFS Plus卷格式 / Unicode细微差别 - hippietrail
1
我在寻找有关OS X标准化的特定信息时遇到了困难。谢谢。 - Keith4G

8
  1. NFC是常用的形式,应该使用它。在NFC中,ä只有一个代码点,这很合理。

  2. NFD适用于某些内部处理 - 如果您想进行无重音搜索或排序,则将字符串放在NFD中会使其更容易和更快速。另一个用途是制作更健壮的短标题。这些只是最明显的用途,我相信还有很多其他用途。

  3. 如果两个字符串x和y是规范等效的,则
    toNFC(x) = toNFC(y)
    toNFD(x) = toNFD(y)

    这是你的意思吗?


3
关于第3点,我不认为这总是正确的。例如(来自维基百科)字符串1包含“U+212B”(埃斯特朗符号“Å”),字符串2包含“U+0041 U+030A”(拉丁字母“A”和上方组合环“°”)。在NFD下,它们是等效的,但在NFC下,字符串2被转换为“U+00C5”(瑞典字母“Å”),因此两者不等效。对我来说,NFD是最安全的选择。http://en.wikipedia.org/wiki/Unicode_equivalence#Normal_forms - Aurimas
1
@Aurimas 这是来自 Unicode 网站的内容:http://www.unicode.org/reports/tr15/tr15-18.html - Esailija
你说得完全正确,我在阅读更多关于这个问题的信息后,正准备修改我的评论。关键在于要先转换为NFD,然后才能进入NFC。 - Aurimas

1
NFC
- 存储时占用的空间较小(意味着在您的进程中占用的RAM较少)。 - 处理速度通常更快,例如在比较字符串或转换为不同编码时(较少的字节意味着需要处理的字节较少)。 - 大多数转换代码自然产生的形式(如果存在一对一映射,为什么要将一个字符转换为多个Unicode码点?使用一对一的简单映射表即可解决问题);因此,如果您知道您的字符串转换函数产生的是NFC,您可以直接使用该字符串。 - 大多数文本/源代码编辑器通常生成的形式,因此文件中的大多数字符串(包括源代码)都以此编码,因此,如果您从已知为NFC的文件中加载文本,则无需进行额外处理。
NFD
通常需要更少的CPU时间来生成,因为NFC通常是由NFD创建的(任何字符-> NFD-> NFC;因此使用NFD,您可以在一半的路程停下来)。
使一些非常特定的操作变得更容易和更快,例如比较没有修饰符的字符(当á和ä和a应该被视为相等字符时,例如在搜索期间,只需跳过修饰符)或者在复制到新字符串时去除修饰符(只需将修饰符留在外面)。
它是由标准定义的许多Unicode转换操作的基础,如果您的字符串是NFC,您首先需要将它们转换(可能是转回)为NFD。
选择哪种代码对您来说重要吗?
实际上并不重要。只要将所有字符串转换并存储为相同的形式,等效的字符串将始终由相同的代码点(也按照指定的顺序)和相同的字节序列组成,因此可以直接使用简单的内存比较进行比较。而且,每个符合标准的用户界面都会在屏幕上显示完全相同的文本,无论使用哪种表示形式。
请注意,一个混合了NFC和NFD的Unicode字符串,以及使用了不符合NFC或NFD标准的代码点组合的字符串,仍然是一个完全有效的Unicode字符串,并被认为与其NFC/NFD形式等效,并且在标准兼容的用户界面中显示相同。这样的字符串是标准允许的,只是没有进行规范化,不能直接与NFC或NFD字符串进行比较。这意味着无论您选择哪种代码,除非您控制字符串的创建并确切知道其形式,否则您必须将来自外部来源的所有字符串视为非规范化,并在直接比较或使用期望任何规范化形式的函数之前,将它们规范化为您选择的形式。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接