处理应用程序中的排序
某些应用程序(如 Microsoft Active Directory、Microsoft Exchange 和 Microsoft Access)维护一个可排序的数据库,其中包含按名称 (UTF-16 字符串) 编制索引的区域设置和语言字符串及其关联的排序权重。
用户 在其自己的区域设置中进行排序通常很直观。 但是,对于应用程序开发人员来说,它可能不直观。 本主题讨论在应用程序中处理排序的注意事项。 排序可以是语言或序号 (非语言) 。
排序函数
可以在应用程序中使用各种排序函数:
- NLS 字符串比较函数。 示例包括 CompareString 和 CompareStringEx、 CompareStringOrdinal、 LCMapString、 LCMapStringEx、 FindNLSString、 FindNLSStringEx 和 FindStringOrdinal。 有关与字符串比较函数相关的安全问题的讨论,请参阅 安全注意事项:国际功能 。
- 在内部调用字符串比较函数的包装函数。 最常见的函数是 lstrcmp 和 lstrcmpi,它们调用 CompareString。
通常排序函数按字符计算字符串。 但是,许多语言都有多字符元素,例如传统西班牙语中的双字符对“CH”。 CompareString 和 CompareStringEx 使用应用程序提供的区域设置标识符或名称来标识多字符元素。 相比之下, lstrcmp 和 lstrcmpi 使用用户的区域设置。
另一个示例是越南语,它包含许多双字符元素,例如“GI”的有效大写、大写和小写形式,分别为“GI”、“Gi”和“gi”。 这些形式中的任何形式都被视为单个排序元素,如果忽略大小写,则比较为相等。 但是,由于“gI”作为单个元素无效, 因此 CompareString、 CompareStringEx、 lstrcmp 和 lstrcmpi 将“gI”视为两个单独的元素。
函数 CompareString、CompareStringEx、lstrcmp、lstrcmpi、LCMapString、LCMapStringEx、FindNLSString 和 FindNLSStringEx 都默认使用“单词排序”技术。 对于这种类型的排序,所有标点符号和其他非字母数字字符(连字符和撇号除外)都位于任何字母数字字符之前。 连字符和撇号的处理方式与其他非字母字符不同,以确保“coop”和“co-op”等单词在排序列表中保持一起。
应用程序可以通过指定SORT_STRINGSORT标志从排序函数请求“字符串排序”技术,而不是单词排序。 字符串排序将连字符和撇号视为任何其他非字母数字字符。 它们在排序序列中的位置位于字母数字字符之前。
下表将单词排序的结果与字符串排序的结果进行比较。
Word排序 | 字符串排序 |
---|---|
坯 | bill's |
条例 草案 | 坯 |
bill's | 条例 草案 |
无法 | 不能 |
不能 | 无法 |
不能 | 不能 |
con | 合作 |
库 珀 | con |
合作 | 库 珀 |
按语言对字符串进行排序
CompareString 和 CompareStringEx 函数测试语言相等性。 应用程序应使用这些具有正确区域设置的函数来对字符串进行语言排序。
注意
为了与 Unicode 兼容,应用程序应首选 CompareStringEx 或 CompareString 的 Unicode 版本。 首选 CompareStringEx 的另一个原因是,出于互操作性的原因,Microsoft 正在迁移到使用区域设置名称而不是新区域设置的区域设置标识符。 仅在 Windows Vista 及更高版本上运行的任何应用程序都应使用 CompareStringEx。
测试语言相等性的另一种方法是使用 lstrcmp 或 lstrcmpi,后者始终使用单词排序。 lstrcmpi 函数使用 NORM_IGNORECASE 标志调用 CompareString,而 lstrcmp 调用它时没有该标志。 有关包装函数用法的概述,请参阅 字符串。
函数检索所有区域设置在语言上适当的结果。 用户对不同区域设置的期望在排序行为方面可能大不相同,如以下示例所示。
- 许多区域设置将 ae 连字 (æ) 与字母 ae 等同。 但是,冰岛 (冰岛) 将其视为单独的字母,并在排序顺序中将其放在 Z 之后。
- A Ring (Å) 通常只以与 A 的音调差进行排序。但是,瑞典 (瑞典) 在排序顺序中将 A Ring 放在 Z 之后。
函数尝试严格验证 Unicode 标准中定义的码位是否与等效码位字符串相等。 例如,表示小写“u”和二元 (ü) 的代码点在音调上等于小写“u”,并结合二元 (ー) 。 但请注意,规范等效并非总是可能的。
由于几乎所有使用 Windows 键盘和输入法编辑器输入的数据 (IME) 都符合 Unicode 标准中定义的 C 规范化形式,因此使用 NLS Unicode 规范化函数转换来自其他平台的传入数据可提供最一致的结果,尤其是对于使用藏语脚本的区域设置或现代朝鲜文朝鲜文脚本的区域设置。 有关 Windows Vista 及更高版本中 Unicode 规范化支持的详细信息,请参阅 使用 Unicode 规范化表示字符串。
当字符串比较遵循用户的语言首选项时,例如,在对排序的 ListView 控件的项进行排序时,应用程序可以执行下列操作之一:
- 使用用户的区域设置调用 lstrcmp 或 lstrcmpi 。
- 调用 CompareString 或 CompareStringEx 以定义比较的区域设置、传递其他标志、嵌入 null 字符或传递显式长度以匹配字符串的各个部分。
如果无论区域设置如何,如果比较结果应该是一致的,例如,将检索到的数据与预定义列表或内部值进行比较时,应用程序应使用 CompareString 或 CompareStringEx ,并将 Locale 参数设置为 LOCALE_INVARIANT。 对于 CompareString,以下任一调用都将匹配,即使 mystr 为“INLAP”。 在这种情况下,如果当前区域设置是越南语,则对 lstrcmpi 的区域设置敏感调用将失败。
在 Windows XP 上:
int iReturn = CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, mystr, -1, _T("InLap"), -1);
在早期操作系统上:
DWORD lcid = MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);
int iReturn = CompareString(lcid, NORM_IGNORECASE, mystr, -1, _T("InLap"), -1);
按顺序对字符串进行排序
对于序号 (非语言) 排序,应用程序应始终使用 CompareStringOrdinal 函数。
注意
此函数仅适用于 Windows Vista 及更高版本。
CompareStringOrdinal 比较两个 Unicode 字符串以测试二进制相等性,而不是语言相等性。 此类非语言字符串的示例包括 NTFS 文件名、环境变量以及互斥体、命名管道或邮件图的名称。 除了不区分大小写的选项外,此函数将忽略所有非二进制等效项。 与其他一些排序函数不同,它测试所有码位是否相等,包括在语言排序方案中未赋予任何权重的码位。
以下所有语句均适用于二进制比较中的 CompareStringOrdinal ,但不适用于 CompareString、 CompareStringEx、 lstrcmp 或 lstrcmpi。
- Unicode 中的音调等效序列(例如拉丁文小写字母 A 与上环 (U+00e5) 和拉丁文小写字母 A + 组合环以上 (U+0061 U+030a) )不相等,即使它们看起来相同 (“å”) 。
- Unicode 中与拉丁文字母 SMALL CAPITAL Y (U+028f) 和拉丁文大写字母 Y (U+0059) (其看起来非常相似 (“ᦛ”和“Y”) 非常相似,并且仅在语言表中的一些特殊大小写权重不同)被视为完全不同的字符。 即使应用程序将 bIgnoreCase 设置为 TRUE,这些字符串也会比较不同。
- 已定义但没有语言排序权重的代码点(例如零宽度 JOINER (U+200d) )被视为具有其代码点权重。
- 在更高版本的 Unicode 中定义但在当前语言表中没有权重的代码点被视为具有其代码点权重。
- Unicode 未定义的代码点被视为具有其代码点权重。
- 当应用程序将 bIgnoreCase 设置为 TRUE 时,函数使用操作系统上层表而不是语言排序表中的信息来映射大小写。 因此,映射独立于区域设置。
有关 Unicode 中规范等效序列和 Unicode 中规范相似的字符串的详细信息,请参阅 使用 Unicode 规范化来表示字符串。
对代码点进行排序
某些 Unicode 码位没有权重,例如零宽度非 JOINER、U+200c。 排序函数有意将无权重代码点评估为等效代码点,因为它们在排序中没有权重。 在 Windows Vista 及更高版本上,应用程序可以通过调用 NLS 字符串比较函数(尤其是 CompareStringOrdinal)对这些代码点进行排序,以评估文本、二进制意义上的所有码位,例如在密码验证中。 在 Windows Vista 之前的操作系统上,应用程序应使用 C 运行时函数 strcmp 或 wcscmp。
当应用程序指定hlink_NONSPACE标志时,排序函数会忽略音调符号,例如 NON SPACING BREVE、U+0306。 同样,指定 hlink_SYMBOLS 标志时,这些函数会忽略符号,例如 EQUALS SIGN、U+003d。 在 Windows Vista 及更高版本上,应用程序调用 CompareStringOrdinal 来评估文本二进制意义上的音调符号和符号码位。 在 Windows Vista 之前的操作系统上,应用程序应使用 strcmp 或 wcscmp。
某些码位(如 0xFFFF 和 0x058b)当前未在 Unicode 中分配。 这些代码点在排序时不接收任何权重,并且永远不应传递给排序函数。 应用程序应使用 IsNLSDefinedString 来检测数据流中的非 Unicode 码位。
注意
IsNLSDefinedString 的结果可能因传递的 Unicode 版本而异,前提是在更高版本中将字符添加到 Unicode 中,并且随后会将其添加到 Windows 排序表中。 有关详细信息,请参阅 使用排序版本控制。
将数字排序为数字
在 Windows 7 及更高版本上,应用程序可以使用 SORT_DIGITSASNUMBERS 标志调用 CompareString、 CompareStringEx、 LCMapString 或 LCMapStringEx 。 此标志支持将数字视为数字的排序,例如,在“10”之前对“2”进行排序。
请注意,此标志的用法不适用于十六进制数字,如下所示。
- 01AF
1BCD
002A
12FA
AB1C
AB02
AB12
在这种情况下,“数字”按顺序排序,但用户会察觉到排序不当的十六进制列表。
映射字符串
如果未指定LCMAP_SORTKEY,则应用程序使用 LCMapString 或 LCMapStringEx 函数来映射字符串。 如果源字符串以 null 结尾,则映射字符串以 null 结尾。
在大写和小写之间进行转换时,函数不保证单个字符将映射到单个字符。 例如,LCMAP_LOWERCASE和LCMAP_UPPERCASE标志可能会将德国夏普 S (“ß”) 映射到自身。 或者,LCMAP_UPPERCASE标志可将“ß”映射到“SS”,LCMAP_LOWERCASE标志可将“SS”映射到“ß”。 该行为取决于 NLS 版本。
在大写和小写之间转换时,函数对上下文不敏感。 例如,虽然 LCMAP_UPPERCASE 标志将希腊文小写 sigma (“σ”) 和希腊文小写最终 sigma (“ς”) 正确映射到希腊文大写 sigma (“Σ”) ,但LCMAP_LOWERCASE标志始终将“Σ”映射到“σ”,从不映射到“ς”。
默认情况下,函数将小写“i”映射到大写的“I”,即使 Locale 参数指定了土耳其语或阿塞拜疆语。 若要替代土耳其语或阿塞拜疆人的此行为,应用程序应指定LCMAP_LINGUISTIC_CASING。 如果使用适当的区域设置指定此标志,“ı” (小写无点 I) 是小写形式“I” (大写无点 I) ,“i” (小写虚线 I) 是小写形式“е” (大写虚线 I) 。
如果指定LCMAP_HIRAGANA标志将片假名字符映射到平假名字符,并且未指定LCMAP_FULLWIDTH, 则 LCMapString 或 LCMapStringEx 仅将全角字符映射到平假名。 在本例中,任何半角片假名字符都作为目标字符串中放置,不映射到平假名。 应用程序必须指定LCMAP_FULLWIDTH,将半角片假名字符映射到平假名。 此限制的原因是所有平假名字符都是全角字符。
如果应用程序需要从源字符串中去除字符,它可以调用映射函数,设置NORM_IGNORESYMBOLS和NORM_IGNORENONSPACE标志,并清除所有其他标志。 如果应用程序使用不以 null 结尾的源字符串执行此操作,则该函数可能会返回空字符串,而不返回错误。
创建排序键
当应用程序指定LCMAP_SORTKEY时, LCMapString 或 LCMapStringEx 将生成排序键,即字节值的二进制数组。 排序键不是真正的字符串,其值表示源字符串的排序行为,但不是有意义的显示值。
注意
函数在生成排序键期间忽略阿拉伯语 kashida。 如果应用程序调用 函数为包含阿拉伯语 kashida 的字符串创建排序键,则函数不会创建排序键值。
排序键可以包含奇数字节。 LCMAP_BYTEREV标志仅反转偶数个字节。 排序键中最后一个字节 (奇位) 不会反转。 如果终止0x00字节是奇数位置的字节,则它仍然是排序键中的最后一个字节。 如果终止0x00字节是一个均匀位置的字节,则它将与其前面的字节交换位置。
生成排序键时,函数以与其他标点符号不同的方式处理连字符和撇号,以便“coop”和“co-op”等单词在列表中保持一起。 除连字符和撇号以外的所有标点符号在字母数字字符之前排序。 应用程序可以通过设置SORT_STRINGSORT标志来更改此行为,如 排序函数中所述。
在 memcmp 中使用时,排序键生成的顺序与 在 CompareString 或 CompareStringEx 中使用源字符串时的顺序相同。 应使用 memcmp 函数而不是 strcmp,因为排序键可以嵌入 null 字节。
使用排序版本控制
排序表有两个标识其版本的数字:定义的版本和 NLS 版本。 这两个数字都是 DWORD 值,由主值和次要值组成。 值的第一个字节是保留的,接下来的两个字节表示主版本,最后一个字节表示次要版本。 在十六进制术语中,模式为 0xRRMMMMmm,其中 R 等于 Reserved,M 等于主要,m 等于次要。 例如,主版本 3(次要版本为 4)表示为 0x304。
定义的版本标识码位的重排,并且对于所有区域设置都是相同的。 主版本递增以指示对现有代码点的更改。 次要版本递增以指示已添加代码点,但以前没有更改现有代码点。
NLS 版本特定于 区域设置标识符 或 区域设置名称,并跟踪对受影响区域设置的代码点权重的更改。 当已可排序的代码点的权重发生更改时,主版本会递增。 当为新码位分配权重时,次要版本会递增,但以前可排序的所有其他码位权重保持不变。
注意
对于主版本,更改一个或多个代码点,以便应用程序必须重新索引所有数据,以便比较有效。 对于次要版本,不会移动任何内容,但添加代码点。 对于此类型的版本,应用程序只需使用以前不可排序的值重新编制字符串索引。
重要
主版本已在 Windows 8 中更改。 在早期版本的 Windows 下创建的数据必须重新编制索引。
定义的和 NLS 版本都适用于使用具有 LCMAP_SORTKEY 标志的 LCMapString 或 LCMapStringEx 函数检索的可排序代码点,也由 CompareString、 CompareStringEx、 FindNLSString 和 FindNLSStringEx 函数使用。 如果字符串中的一个或多个代码点不可排序,则在将该字符串作为参数传递给该字符串时 ,IsNLSDefinedString 函数将返回 FALSE 。
应用程序可以调用 GetNLSVersion 或 GetNLSVersionEx 来检索排序表的已定义版本和 NLS 版本。
为数据库编制索引
出于性能原因,应用程序在为数据库编制索引时应遵循此过程。
正确为数据库编制索引
- 对于每个函数,存储 NLS 版本、该版本的排序键,以及每个索引字符串的排序指示。
- 当次要版本递增时,重新索引以前不可排序的字符串。 此更新中受影响的字符串应限制为 IsNLSDefinedString 之前已返回 FALSE 的字符串。
- 当主版本递增时,重新索引所有字符串,因为更新的权重可能会更改任何字符串的行为。 主要版本版本很少发布。
数据库索引编制问题的原因如下:
- 更高版本的操作系统可以定义未为早期操作系统定义的代码点,从而更改排序。
- 由于语言支持方面的更正,代码点在不同的操作系统中可以具有不同的排序权重。
为了最大程度地减少在这些情况下重新索引数据库的必要性,应用程序可以使用 IsNLSDefinedString 将定义的字符串与未定义的字符串区分开来,以便应用程序可以拒绝具有未定义码位的字符串。 使用 GetNLSVersion 或 GetNLSVersionEx ,应用程序可以确定 NLS 更改是否影响用于特定索引表的区域设置。 如果更改对区域设置没有影响,则应用程序无需重新为表编制索引。
示例
下表说明了与排序函数一起使用的某些标志的影响。 在每种情况下,选择标志都会确定两个不同的字符是否被视为相等的排序。
字符 1 | 字符 2 | 默认 | NORM_IGNOREWIDTH | NORM_IGNOREKANA | NORM_IGNOREWIDTH |NORMIGNOREKANA |
---|---|---|---|---|---|
"あ" U+3042 平假名字母 A |
"ガ" U+30A2 片假名字母 A |
不等 | 不等 | 等于 | 等于 |
"オ" U+FF75 半形片假名字母 O |
"オ" U+30AA 片假名字母 O |
不等 | 等于 | 不等 | 等于 |
“B” U+FF22 全形拉丁文大写字母 B |
"B" U+0042 拉丁文大写字母 B |
不等 | 等于 | 不等 | 等于 |
相关主题