處理應用程式中的排序
某些應用程式,例如 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函式都預設為使用「word sort」 技術。 針對這種類型的排序,除了連字號和單引號以外,所有標點符號和其他非虛設字元都會在任何英數位元之前。 連字號和單引號的處理方式與其他非虛構字元不同,以確保 「coop」 和 「co-op」 之類的單字會保持在已排序的清單中。
應用程式可以藉由指定SORT_STRINGSORT旗標,從排序函式要求「字串排序」技術,而不是單字排序。 字串排序會將連字號和單引號視為任何其他非虛設字元。 它們在排序序列中的位置在英數位元之前。
下表比較文字排序的結果與字串排序的結果。
Word排序 | 字串排序 |
---|---|
坯 | 帳單的 |
條例 草案 | 坯 |
帳單的 | 條例 草案 |
無法 | 不能 |
不能 | 無法 |
不能 | 不能 |
con | 共同作業 |
庫 珀 | con |
共同作業 | 庫 珀 |
以語言方式排序字串
CompareString和CompareStringEx函式會測試語言是否相等。 您的應用程式應該使用這些函式搭配正確的地區設定,以語言方式排序字串。
注意
為了與 Unicode 相容,應用程式應該偏好 CompareStringEx 或 CompareString的 Unicode 版本。 偏好 CompareStringEx 的另一個原因是,基於互通性考慮,Microsoft 正移轉至使用地區設定名稱,而不是使用地區設定識別碼。 只有在 Windows Vista 和更新版本上執行的任何應用程式都應該使用 CompareStringEx。
測試語言相等的另一種方式是使用 lstrcmp 或 lstrcmpi,這一律使用單字排序。 lstrcmpi函式會使用 NORM_IGNORECASE 旗標呼叫CompareString,而lstrcmp 會呼叫它而不使用該旗標。 如需使用包裝函式的概觀,請參閱 字串。
函式會擷取所有地區設定的語言適當結果。 不同地區設定的使用者期望在排序行為方面可能會明顯不同,如下列範例所示。
- 許多地區設定都會將 ae ligature (æ) 與字母 ae 相等。 不過, (愛爾蘭) 將它視為個別字母,並將它放在排序次序中的 Z 之後。
- A Ring (Å) 通常會以與 A 的變音符號差異來排序。不過,瑞典文 (瑞典) 將 A Ring 放在排序次序中的 Z 之後。
函式會嘗試嚴格地確認 Unicode 標準中定義的字碼點與對等字碼指標的字串相同。 例如,以 dieresis (ü) 代表小寫 「u」 的程式碼點,與小寫 「u」 與小寫 「u」 結合 (ー) 。 不過請注意,標準等價不一定可行。
由於幾乎所有使用 Windows 鍵盤和輸入法編輯器輸入的資料都 (IME) 符合 Unicode 標準中定義的 C 正規化形式,因此使用 NLS Unicode 正規化函式從其他平臺轉換傳入資料可提供最一致的結果,特別是針對使用 Modern script 或 Hangul 腳本進行新式韓文的地區設定。 如需 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 檔案名、環境變數,以及 mutex、具名管道或 mailslots 的名稱。 除了不區分大小寫的選項之外,此函式會忽略所有非二進位等價。 不同于某些其他排序函式,它會測試所有程式碼點是否相等,包括未在語言排序配置中給予任何加權的程式碼點。
下列所有語句都適用于二進位比較中的CompareStringOrdinal,但不適用於CompareString、CompareStringEx、lstrcmp或lstrcmpi。
- Unicode 中的 Canonically 對等序列,例如 LATIN SMALL LETTER A WITH RING ABOVE (U+00e5) 和 LATIN SMALL LETTER A + COMBINING RING ABOVE (U+0061 U+030a) ,即使它們顯示為相同的 (「å」) 也一樣。
- Unicode 中的 Canonically 類似字串,例如 LATIN LETTER SMALL CAPITAL Y (U+028f) 和 LATIN CAPITAL LETTER Y (U+0059) ,看起來非常類似 (「ᦛ」 和 「Y」) ,而且只會因語言資料表中的某些特殊大小寫權數而有所不同, 會被視為完全不同的字元。 即使應用程式將 bIgnoreCase 設定為 TRUE,這些字串也會比較為不同。
- 已定義但沒有語言排序權數的程式碼點,例如零寬度 JOINER (U+200d) ,會被視為具有其字碼點權數。
- 在較新版本 Unicode 中定義的字碼點,但目前語言資料表中沒有權數,會被視為具有其字碼點權數。
- Unicode 未定義的代碼點會被視為具有其程式碼點權數。
- 當應用程式將 bIgnoreCase 設定為 TRUE時,函式會使用作業系統大寫資料表來對應大小寫,而不是語言排序資料表中的資訊。 因此,對應與地區設定無關。
如需 Unicode 中 Canonically 對等序列和 Unicode 中標準相似字串的詳細資訊,請參閱 使用 Unicode 正規化來代表字串。
排序代碼點
某些 Unicode 字碼點沒有權數,例如零寬度 NON 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。
Unicode 中目前未指派某些代碼點,例如0xFFFF和0x058b。 這些程式碼點不會在排序中收到任何權數,且絕對不應該傳遞至排序函式。 應用程式應該使用 IsNLSDefinedString 來偵測資料流程中的非 Unicode 字碼點。
注意
IsNLSDefinedString的結果可能會因字元在更新版本中新增至 Unicode 而後續新增至 Windows 排序資料表的 Unicode 版本而有所不同。 如需詳細資訊,請參閱 使用排序版本設定。
將數位排序為數字
在 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旗標可能會將德文 Sharp 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) 是 「İ」 (大寫虛線 I) 小寫形式。
如果指定LCMAP_HIRAGANA旗標將片假名字元對應至平假名字元,且未指定LCMAP_FULLWIDTH, 則 LCMapString 或 LCMapStringEx 只會將全形字元對應至平假名。 在此情況下,任何半形片假名字元會放在目的地字串中,且不會對應到平假名。 應用程式必須指定LCMAP_FULLWIDTH,將半形片假名字元對應至平假名。 此限制的原因是所有平假名字元都是全形字元。
如果應用程式需要從來源字串移除字元,則可以使用已設定的NORM_IGNORESYMBOLS和NORM_IGNORENONSPACE旗標,以及清除所有其他旗標來呼叫對應函式。 如果應用程式以非 Null 終止的來源字串執行這項作業,函式可能會傳回空字串,而不會傳回錯誤。
建立排序索引鍵
當應用程式指定LCMAP_SORTKEY時, LCMapString 或 LCMapStringEx 會產生排序索引鍵,這是位元組值的二進位陣列。 排序索引鍵不是 true 字串,而且其值代表來源字串的排序行為,但不是有意義的顯示值。
注意
函式會在產生排序索引鍵時忽略阿拉伯文 kashida。 如果應用程式呼叫 函式來建立包含阿拉伯文 kashida 之字串的排序索引鍵,則函式不會建立排序索引鍵值。
排序索引鍵可以包含奇數的位元組數。 LCMAP_BYTEREV旗標只會反轉偶數個位元組。 排序索引鍵中的最後一個位元組 (奇數位置) 不會反轉。 如果終止0x00位元組是奇數位置的位元組,它會保留在排序索引鍵的最後一個位元組。 如果終止0x00位元組是偶數位節,則會與前面位的位元組交換位置。
產生排序索引鍵時,函式會以與其他標點符號不同的方式處理連字號和標點符號,讓 「coop」 和 「co-op」 等字放在清單中。 連字號和引號以外的所有標點符號都會在英數位元之前排序。 應用程式可以藉由設定SORT_STRINGSORT旗標來變更此行為,如 排序函式中所述。
在 memcmp中使用時,排序索引鍵會產生與 在 CompareString 或 CompareStringEx中使用來源字串時相同的順序。 memcmp函式應該使用,而不是strcmp,因為排序索引鍵可以有內嵌的 Null 位元組。
使用排序版本控制
排序資料表有兩個數字可識別其版本:已定義的版本和 NLS 版本。 這兩個數字都是 DWORD 值,由主要值和次要值組成。 值的第一個位元組是保留的,接下來的兩個位元組代表主要版本,最後一個位元組代表次要版本。 在十六進位詞彙中,模式為 0xRRMMMMmm,其中 R 等於 Reserved、M 等於主要,而 m 等於次要。 例如,次要版本為 4 的主要版本 3 會以0x304表示。
定義的版本會識別程式碼點的複本,而且所有地區設定都相同。 主要版本會遞增,以指出現有程式碼點的變更。 次要版本會遞增,以指出已新增程式碼點,但尚未變更任何先前現有的程式碼點。
NLS 版本專屬於 地區設定識別碼 或 地區設定名稱,並追蹤受影響地區設定之程式碼點權數的變更。 當已可排序之程式碼點的權數變更時,主要版本會遞增。 次要版本會在指派新代碼點權數時遞增,但所有其他先前可排序的程式碼點權數都會保持不變。
注意
針對主要版本,會變更一或多個程式碼點,讓應用程式必須重新編制所有資料的索引,才能讓比較有效。 對於次要版本,不會移動任何專案,但會新增程式碼點。 針對這種類型的版本,應用程式只需要重新編制具有先前不可排序值的字串索引。
重要
主要版本已在 Windows 8中變更。 在舊版 Windows 下建立的資料必須重新編制索引。
已定義的 和 NLS 版本都適用于使用LCMapString或LCMapStringEx函式搭配 LCMAP_SORTKEY 旗標擷取的可排序程式代碼點,也由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 HIRAGANA LETTER A |
"ガ" U+30A2 片假名字母 A |
不等 | 不等 | 等於 | 等於 |
"オ" U+FF75 HALFWIDTH 片假名字母 O |
"オ" U+30AA 片假名字母 O |
不等 | 等於 | 不等 | 等於 |
「B」 U+FF22 FULLWIDTH 拉丁大寫字母 B |
"B" U+0042 拉丁大寫字母 B |
不等 | 等於 | 不等 | 等於 |
相關主題