循序渐进全球化
排序与字符串比较
本页内容
概述和说明
Win32 中的排序与字符串比较
.NET Framework 中的排序与字符串比较
引用
概述和说明
尽管您认为您已经了解了排序列表所涉及的全部问题,但全球通用应用程序的用户可能对“排序”列表的构成有完全不同的期望。不同语言之间不仅字母顺序不同,而且字典和电话簿中的项目排序约定也可能大相径庭。
例如,在瑞典语中,一些带重音符号的元音排在 "Z" 之后,而在其他一些欧洲国家/地区中,同样带有重音符号的元音却紧跟在不带音调符号的元音之后。包括非拉丁语脚本字符的语言具有一些特殊的排序规则。亚洲语言可按拼音、部首顺序、笔画数等多种方式进行排序。
字符串的排序和比较是特定于语言的。即使在基于拉丁语脚本的语言中,也有不同的构成和排序规则。因此,在进行排序和字符串比较时,依靠的并不是码点。
返回页首
Win32 中的排序与字符串比较
字符串比较
lstrcmp 和 lstrcmpi Win32 函数分别以区分大小写或不区分大小写的方式比较两个字符串,并采用针对当前所选的用户区域设置定义的排序规则和标准。lstrcmp 和 lstrcmpi 函数在比较两个字符串时,会对两个字符串从第一个字符开始逐个字符进行比较,直到发现不一致的字符或到达字符串结尾为止。这些函数会返回它们遇到的第一个不相等字符之间的差值。例如,lstrcmp 确定 "abcz" 大于 "abcdefg",然后返回与 "z" 和 "d" 对应的码点编号之间的差值。所选的用户区域设置可确定哪个字符串更大(或字符串是否相同)。
这些 API 的最大缺点是无法在进行比较时指定应该使用哪些特定于区域设置的规则,而只能始终使用当前所选的用户区域设置标准。在下列情况下,这可能会产生问题:
- 您想要在客户端上下文中显示数据,因此需要使用用户区域设置规范在客户端进行比较。您想要进行可识别区域设置的字符串比较,但希望使用当前用户区域设置以外的其他区域设置规则。CompareString API 允许您比较两个字符串并指定在比较时要使用的区域设置。
- 为防止特定于区域设置的构成或排序规则影响比较结果,在进行比较时应独立于区域设置。假设您从注册表中检索某种类型的数据(如 "HorzScroll"),并希望使用 lstrcmpi 将该数据与预定义项目列表(如 "HORZSCROLL")进行比较。这是在应用程序内部执行的,您始终希望在同一条件下执行比较。但遗憾的是,"sc" 在匈牙利语中是一个特殊的复合词(与 "Sc" 相对)。因而,如果用户区域设置被设置为匈牙利,则比较操作总是无法成功!在这种情况下,您应该做的就是进行不区分区域设置(或独立于区域设置)的比较。
Windows XP 引入了一个名为“不变区域设置”(LOCALE_INVARIANT 是它的预定义标记)的新区域设置 ID,可使用它来进行此类比较。独立于区域设置的字符串比较必须使用这一新的区域设置 ID。
CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, TEXT("HorzScroll"), -1, TEXT("HorzScroll"), -1);
通过使用不变区域设置,无论用户区域设置及其相关的排序规则是什么,您都可以检索到相同的比较结果。但是,不变区域设置仅在 Windows XP 中可用。因此,对于低级平台,您需要定义自己的区域设置(如英语),然后使用遵循 ASCII 字符映射的基本英语语言比较规则,如本例中所示。此代码应类似于下方所示:
LCID Locale;Locale = MAKELCID(MAKELANGID (LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT);ComapareString(Locale, ..., ..., ..., ...);
在这种方法中,所有非英语字符(英语(美国)没有为其定义任何排序规则)将按其默认排序顺序进行排序。执行此操作后,肯定将会检索到一致的结果。
重要事项 使用 CompareString 进行比较时,必须格外注意以下标记的使用:
- NORM_IGNOREKANATYPE。此标记会将要比较的相应平假名字符与片假名字符视为相等。
- NORM_IGNORENONSPACE。此标记会将要比较的组合字符(音调符号)与非组合字符视为相等。以日语为例 将等于. 对于拉丁语脚本,"ö" 将等于 "o"。
字符串排序
在已经设置了排序属性的列表视图控件中显示数据时,可以利用操作系统提供的排序支持。编辑控件将根据特定于当前所选用户区域设置的语言排序规则来排序项目。但是,如果您想要自行排序,则实际解决方案将取决于您的需求。
如果只排序少量元素,则最快捷而又最简单的解决方案是使用之前介绍的字符串比较方法,在比较完毕后根据这些比较结果对元素进行排序。但是,如果要排序大量元素,则此方法并不适用。由于 CompareString 的算法较为复杂,因此通过检索排序关键字的方式来排序一长串单词列表会比较快捷。
高级排序算法应遵循以下常规模型:
- 为每个新元素生成一个排序关键字。
- 通过使用 memcmp 比较其排序关键字的值来比较这些字符串。
- 将排序关键字值与字符串一起存储,以避免在每次重新生成它们时花费额外的处理时间。
排序关键字是排序元素的复合表示。(请参见下图。)排序关键字的每个部分都具有不同的权重,具体取决于与区域设置相关联的排序规则。元素在进行排序时可以根据脚本(即所有希腊语字符在一起排列、所有西里尔字符在一起排列…)、字母数字字符和符号字符、音调符号、大小写字母及其他特殊规则,例如将两个字符作为单个字符排序(比如西班牙语的 "CH")。预定义常量 SORT_DEFAULT 指出通常与给定语言 ID 相关联的哪种排序算法是首选算法。
与德语单词 "öde"(意思是“荒地”)对应的 Win32 排序关键字。
排序关键字中的前三个值代表字符串中每个字符的 Unicode 排序权重。接下来的三个值代表音调符号权重,最后三个值代表大小写权重。在这几个部分之间有一个分隔符值。排序关键字以空结束符结束。
下面的示例代码包含一个搜索引擎程序,用来将词汇标记映射到排序关键字。您将注意到,搜索引擎通过在每个排序关键字的前面加上一个由应用程序定义的标记来自定义排序关键字,以确保首先排序单词,然后再排序数字和标点符号。(系统的默认行为是先排序标点符号,然后再排序数字和字母。)
// sort key generation from UnicodeBYTE* pbSource; // multibyte string bufferint cbSource; // size of pbSource bufferint LCSortKey( LCID lcid, // locale ID LPCTSTR pwSource, // address of source string int cwSource, // number of characters in source string LPTSTR pwDest, // address of destination buffer int cwDest) // size of destination buffer{ int nRet; // size of sort key in words nRet = LCMapString(lcid, LCMAP_SORTKEY, pwSource, cwSource, pwDest+1, (cwDest-1)<<1) >> 1; if (nRet) { nRet++; if (cwDest && pwDest) // Set a sort key's prefix so tokens // group first by alphabetics, then // by numerics, then by punctuation. { BYTE bCharType = char_types(*pwSource); *pwDest = ~(bCharType & WORD_TYPE); } }return nRet;}
搜索引擎对词语列表排序完之后,可以使用二进制搜索算法快速查找匹配的字符串。使用排序关键字非常方便,因为它们会自动处理特定于语言和特定于区域设置的排序首选项。一个相关示例是匹配连字。(在英语中,"Æ" 等同于 "AE",但在丹麦语中并非如此。)另一个相关示例是单字符与双字符组合之间的区别。(在一些西班牙语区域设置中,"l" 和组合 "ll" 是不同的字符。)
以下是语言排序的一些特征:
- 语言的书写系统将确定影响语言排序顺序的内容。例如,俄语的排序顺序基于西里尔字母(也可能是音调符号),但日语的排序顺序可能基于字符的笔画数。
- 语言排序顺序不同于 Unicode 码点顺序。
- 使用相同脚本的语言通常具有不同的语言排序顺序。
- 排序元素(如字符)可以是多个 Unicode 码点的组合。例如,U+0B95 + U+0BCD + U+0BB7 = 泰米尔语 Ksha(泰米尔语中的一个单独的排序元素)。
请务必要知道,使用 Windows 2000 和 Windows XP 系统支持,可以自动为您处理所有这些排序问题。
返回页首
.NET Framework 中的排序与字符串比较
字符串比较
CompareInfo 类提供了一组可用来执行区分文化的字符串比较方法。CultureInfo.CompareInfo 属性(CultureInfo 类的一个实例)可定义如何针对特定文化来比较和排序字符串。String.Compare 方法使用 CultureInfo.CompareInfo 属性中的信息来比较字符串。如果 string1 小于 string2,此方法返回一个负整数;如果 string1 和 string2 相等,则返回零 (0);如果 string1 大于 string2,则返回一个正整数。
以下代码示例将说明如何根据进行比较时所使用的文化,通过 String.Compare 方法以不同的方式来计算两个字符串。首先,将 CurrentCulture 设置为丹麦语(丹麦),并比较字符串 "Apple" 和 "Æble"。丹麦语将字符 "Æ" 视为一个单独的字母,在字母表中将其排在 "Z" 之后。因此对于丹麦语文化,字符串 "Æble" 大于 "Apple"。接下来,将 CurrentCulture 设置为英语(美国),再次比较字符串 "Apple" 和 "Æble"。这一次,字符串 "Æble" 被认为小于 "Apple"。英语语言将字符 "Æ" 视为一个特殊符号,在字母表中将其排在字母 "A" 之前。
using System;using System.Globalization;using System.Threading;public class CompareStringSample{
public static void Main()
{
string str1 = "Apple";
string str2 = "Æble";
// Set the CurrentCulture to Danish in Denmark.
Thread.CurrentThread.CurrentCulture = new CultureInfo("da-DK");
// Compare the two strings.
int result1 = String.Compare(str1, str2);
Console.WriteLine("\nWhen the CurrentCulture is \"da-DK\",\n");
Console.WriteLine("the result of comparing {0} with {1} is: {2}",
str1, str2, result1);
// Set the CurrentCulture to English in the U.S.
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
// Compare the two strings.
int result2 = String.Compare(str1, str2);
Console.WriteLine(
"\nWhen the CurrentCulture is \"en-US\",\n");
Console.WriteLine(
"the result of comparing {0} with {1} is: {2}",
str1, str2, result1);
}}
如果执行此代码,输出将如下所示:
When the CurrentCulture is "da-DK",the result of comparing Apple with Æble is: -1When the CurrentCulture is "en-US",the result of comparing Apple with Æble is: 1
对于不会直接显示给最终用户的排序数据,InvariantCulture 非常有用。以独立于文化的格式排序数据可保证已知格式不会发生改变。来自不同文化的用户访问该数据时,它可以根据不同的用户进行相应的格式设置。可使用空字符串 ("") 或使用 CultureInfo.InvariantCulture 通过不变文化来初始化 CultureInfo,如下所示:
// The following lines are equivalent.CultureInfo Invc = New CultureInfo("");CultureInfo Invc = CultureInfo.InvariantCulture;
替代排序顺序
一些文化可支持多种排序顺序。例如,文化 "zh-CN"(中国中文)支持按发音(默认)和按笔画数排序。使用文化名称(如 "zh-CN")创建 CultureInfo 对象时,将使用默认排序顺序。要指定替代排序顺序,请使用替代排序顺序的 LCID 创建一个 CultureInfo 对象。然后,从 CultureInfo.CompareInf 获取一个 CompareInfo 对象以用于字符串比较。或者,也可以使用 CompareInfo.GetCompareInfo 方法 (Int32) 并指定替代排序顺序的 LCID 来直接创建一个 CompareInfo 对象。
下表列出了支持替代排序顺序的文化以及用于默认和替代排序顺序的 LCID。
文化名称 | 语言(国家/区域) | 默认排序名称和 LCID | 替代排序名称和 LCID | ||||
es-ES
|
西班牙语(西班牙) |
|
|
||||
zh-TW
|
繁体中文(中国台湾) |
|
|
||||
zh-CN
|
简体中文(中国) |
|
|
||||
zh-HK
|
中文(中国香港特别行政区) |
|
|
||||
zh-SG
|
中文(新加坡) |
|
|
||||
zh-MO
|
中文(澳门特别行政区) |
|
|
||||
de-DE
|
德语(德国) |
|
|
||||
hu-HU
|
匈牙利语(匈牙利) |
|
|
||||
ka-GE
|
格鲁吉亚语(格鲁吉亚) |
|
|
字符串排序
Array 类提供了一个重载的 Array.Sort 方法,通过它可以根据 CultureInfo.CurrentCulture 属性来排序数据。在以下示例中,将创建一个由三个字符串组成的数组。首先,将 CurrentCulture 设置为 "en-US" 并调用 Array.Sort 方法。生成的排序顺序采用的是 "en-US" 文化的排序约定。接下来,将 CurrentCulture 设置为 "da-DK",并再次调用 Array.Sort 方法。注意此排序顺序的生成方式不同于 "en-US" 的结果,因为这次使用的是 "da-DK" 文化的排序约定。
using System;using System.Threading;using System.Globalization;public class ArraySort{
public static void Main(String[] args)
{
String str1 = "Apple";
String str2 = "Æble";
String str3 = "Zebra";
// Create and initialize a new Array instance to store
// the strings.
Array stringArray = Array.CreateInstance( typeof(String), 3 );
stringArray.SetValue(str1, 0 );
stringArray.SetValue(str2, 1 );
stringArray.SetValue(str3, 2 );
// Display the values of the Array.
Console.WriteLine( "\nThe Array contains the following strings:");
PrintIndexAndValues(stringArray);
// Set the CurrentCulture to "en-US".
Thread.CurrentThread.CurrentCulture = new CultureInfo("enUS");
// Sort the values of the Array.
Array.Sort(stringArray);
// Display the values of the Array.
Console.WriteLine(
"\nAfter sorting for the culture \"en-US\":" );
PrintIndexAndValues(stringArray);
// Set the CurrentCulture to "da-DK".
Thread.CurrentThread.CurrentCulture = new CultureInfo("daDK");
// Sort the values of the Array.
Array.Sort(stringArray);
// Display the values of the Array.
Console.WriteLine(
"\nAfter sorting for the culture \"da-DK\":" );
PrintIndexAndValues(stringArray);
}public static void PrintIndexAndValues(Array myArray)
{
for ( int i = myArray.GetLowerBound(0);
i <= myArray.GetUpperBound(0); i++ )
Console.WriteLine( "\t[{0}]:\t{1}", i, myArray.GetValue( i ) );
}}
此代码将产生以下输出:
数组最初包含以下字符串:
[0]: Apple[1]: Æble[2]: Zebra
在针对文化 "en-US" 排序后:
[0]: Æble[1]: Apple[2]: Zebra
在针对文化 "da-DK" 排序后:
[0]: Apple[1]: Zebra[2]: Æble
使用排序关键字
再次重申,排序关键字用于支持在不同文化之间有所变化的排序方法。根据排序关键字方法,字符串中的每个字符都被赋予了多个类别的排序权重,包括字母、大小写和音调符号权重等。对于特定字符串,排序关键字可作为这些权重的存储库。例如,某个排序关键字可能依次包含一串字母权重、一串大小写权重等等。
在 .NET Framework 中,SortKey 类将字符串映射到其排序关键字,反之亦然。您可以使用 CompareInfo.GetSortKey 方法为指定的字符串创建排序关键字。针对指定字符串生成的排序关键字是一个字节序列,它会根据您指定的 CurrentCulture 和 CompareOptions 的不同而变化。例如,如果在创建排序关键字时指定了 IgnoreCase,则使用此排序关键字进行字符串比较时将忽略大小写。
为字符串创建了排序关键字之后,可以将其作为参数传递给 SortKey 类所提供的方法。SortKey.Compare 方法允许您对排序关键字进行比较。由于 SortKey.Compare 执行的是简单的逐字节比较,因此它比使用 String.Compare 要快很多。在用于排序的应用程序中,通过生成应用程序使用的所有字符串的排序关键字并将其存储下来,可以改进应用程序的性能。当需要进行排序或比较操作时,可以使用排序关键字而不是字符串。
以下代码示例可在 CurrentCulture 被设置为 "da-DK" 后,为两个字符串创建排序关键字。它使用 SortKey.Compare 方法来比较这两个字符串并显示结果。如果 string1 小于 string2,SortKey.Compare 方法返回一个负整数;如果 string1 和 string2 相等,则返回零 (0);如果 string1 大于 string2,则返回一个正整数。接下来,将 CurrentCulture 设置为 "en-US" 文化,然后为相同的字符串创建排序关键字。字符串的排序关键字被加以比较并显示出结果。请注意,排序结果会根据 CurrentCulture 的不同而异。虽然以下代码示例的结果与之前给出的 String.Compare 示例中的这些字符串比较结果相同,但 SortKey.Compare 方法要比 String.Compare 方法更快。
using System;using System.Threading;using System.Globalization;public class SortKeySample
{
public static void Main(String[] args)
{
String str1 = "Apple";
String str2 = "Æble";
// Set the CurrentCulture to "da-DK".
CultureInfo dk = new CultureInfo("da-DK");
Thread.CurrentThread.CurrentCulture = dk;
// Create a culturally sensitive sort key for str1.
SortKey sc1 = dk.CompareInfo.GetSortKey(str1);
// Create a culturally sensitive sort key for str2.
SortKey sc2 = dk.CompareInfo.GetSortKey(str2);
// Compare the two sort keys and display the results.
int result1 = SortKey.Compare(sc1, sc2);
Console.WriteLine(
"\nWhen the CurrentCulture is \"da-DK\",\n");
Console.WriteLine("\the result of comparing {0} with {1} is: {2}",
str1, str2, result1);
// Set the CurrentCulture to "en-US".
CultureInfo enus = new CultureInfo("en-US");
Thread.CurrentThread.CurrentCulture = enus ;
// Create a culturally sensitive sort key for str1.
SortKey sc3 = enus.CompareInfo.GetSortKey(str1);
// Create a culturally sensitive sort key for str1.
SortKey sc4 = enus.CompareInfo.GetSortKey(str2);
// Compare the two sort keys and display the results.
int result2 = SortKey.Compare(sc3, sc4);
Console.WriteLine(
"\nWhen the CurrentCulture is \"en-US\",\n");
Console.WriteLine(
"\the result of comparing {0} with {1} is: {2}",
str1, str2, result2);
}
}
此代码将产生以下输出:
When the CurrentCulture is "da-DK",the result of comparing Apple with Æble is: -1When the CurrentCulture is "en-US",the result of comparing Apple with Æble is: 1
除了处理不同的排序顺序以外,根据区域设置约定来表示数字是您将遇到的另一个挑战。在后续部分将说明应如何更好地应对这一挑战。
返回页首
引用
有关东亚语言的断行和断字的更多信息。
返回页首 | 第 2 页,共 11 页 |