深入理解蘋果系統(Unicode)字元串的排序方法

来源:https://www.cnblogs.com/qcloud1001/archive/2018/11/20/9987416.html
-Advertisement-
Play Games

歡迎大家前往 "騰訊雲+社區" ,獲取更多騰訊海量技術實踐乾貨哦~ 本文由 "iminder" 發表於 "雲+社區專欄" Unicode編碼 我們知道電腦是不能直接處理文本的,而是和數字打交道。因此,為了表示文本,就建立了一個字元到數字的映射表,叫做編碼。最著名的字元編碼就是ASCII了,它使用7 ...


歡迎大家前往騰訊雲+社區,獲取更多騰訊海量技術實踐乾貨哦~

本文由iminder發表於雲+社區專欄

Unicode編碼

我們知道電腦是不能直接處理文本的,而是和數字打交道。因此,為了表示文本,就建立了一個字元到數字的映射表,叫做編碼。最著名的字元編碼就是ASCII了,它使用7-bit來表示應用字母表以及數字和其他字元。這對於英語來說是夠用了,但是對於其他語言,這個7-bit就不能滿足條件了,因為字元遠遠超過了7-bit所能表示的最大個數。因此1987年,來自幾個大的科技公司的工程師開始合作開發一種致力於能在全世界的所有書寫系統中都能通用的字元編碼系統,並與1991年10發佈了Unicode的1.0.0標準。2018年6月發佈了Unicode的11.0版本。這裡就不再對Unicode做過多的介紹,值得註意的是,在iOS開發中,常使用的的NSString是基於Unicode-16來開發的,這是因為當時開發這個的時候Unicode標準還是以16bit固定長度來編碼,這就導致使用上的一些坑,建議大家閱讀下這篇文章:NSString and Unicode

#UCA和CLDR:最常用到的排序標準

介紹完Unicode編碼之後,我們就可以來介紹UCA(Unicode Collation Algorithm)和CLDR(Common Locale Data Repository)了,因為蘋果的NSString的介紹文檔里有這麼一句話:

Localized string comparisons are based on the Unicode Collation Algorithm, as tailored for different languages by CLDR (Common Locale Data Repository). Both are projects of the Unicode Consortium. Unicode is a registered trademark of Unicode, Inc.

說白了,蘋果系統的NSString字元串排序是基於UCA的,並且在不同語言下,經過CLDR來裁剪的。

UCA(Unicode Collation Algorithm)

UCA的介紹官方文檔介紹在這裡:UCA介紹。其中第一句話就寫的很清楚,

Collation is the general term for the process and function of determining the sorting order of strings of characters.

對字元串排序的過程就是Collation,UCA就是Unicode表示的字元串進行排序的規則,制定這個規則的原因是不同語種對字元串的排序規則要求是不一樣的,比如,德國、法國和瑞士對相同的字元排序的規則是不一樣的,甚至在同一個語言下比如中文,多音字這種在不同組合里,排序的先後順序也是不一樣的。

img差異化舉例

因此可以想象,UCA指定的規則比較複雜。感興趣的可以讀下前面貼的UCA介紹,裡面有具體的排序規則介紹。

CLDR(Common Locale Data Repository)

CLDR的官方文檔在這裡:CLDR介紹。CLDR是一堆語言數據倉庫,為軟體提供各種世界語言版本提供了基礎,目前在使用CLDR的公司有:

Apple (macOS, iOS, watchOS, tvOS, and several applications; Apple Mobile Device Support and iTunes for Windows; …) Google (Web Search, Chrome, Android, Adwords, Google+, Google Maps, Blogger, Google Analytics, …) IBM (DB2, Lotus, Websphere, Tivoli, Rational, AIX, i/OS, z/OS,…) Microsoft (Windows, Office, Visual Studio, …)

其他公司:

ABAS Software, Adobe, Amazon (Kindle), Amdocs, Apache, Appian, Argonne National Laboratory, Avaya, Babel (Pocoo library), BAE Systems Geospatial eXploitation Products, BEA, BluePhoenix Solutions, BMC Software, Boost, BroadJump, Business Objects, caris, CERN, Debian Linux, Dell, Eclipse, eBay, EMC Corporation, ESRI, Firebird RDBMS, FreeBSD, Gentoo Linux, GroundWork Open Source, GTK+, Harman/Becker Automotive Systems GmbH, HP, Hyperion, Inktomi, Innodata Isogen, Informatica, Intel, Interlogics, IONA, IXOS, Jikes, jQuery, Library of Congress, Mathworks, Mozilla, Netezza, OpenOffice, Oracle (Solaris, Java), Lawson Software, Leica Geosystems GIS & Mapping LLC, Mandrake Linux, OCLC, Perl, Progress Software, Python, QNX, Rogue Wave, SAP, Shutterstock, SIL, SPSS, Software AG, SuSE, Symantec, Teradata (NCR), ToolAware, Trend Micro, Twitter, Virage, webMethods, Wikimedia Foundation (Wikipedia), Wine, WMS Gaming, XyEnterprise, Yahoo!, Yelp

對於不同區域(local),可以找到不同的數據CLDR,結合UCA對字元串進行排序,就做到了不同語言下的本地化排序。可以去 http://cldr.unicode.org/ 下載最新的CLDR庫,後面將會用到裡面的一些內容。

字元分類與排序規則

字元分類與Unicode碼點值排序

Unicode把所有的字元分為兩類:

  1. common charaters 包括空格,標點,通用符號,貨幣符號,數字等。
  2. script charaters 包括拉丁字母,希臘字母,漢字等。 這樣經過分類,便於把一類字元統一集中在一起。

通常情況下,我們是通過unicode 的UTF-16碼點值逐個進行比較大小的來進行排序的。

NSArray *rawArray = @[@"愛你", @"一生一世",@"㊀", @"上",@"㊤",@"μ",@"язык",@"..",@"123",@"@",@"AA",@"abc",@"abb"];
//1. 預設排序方式
NSArray *defaultedSortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
    return [obj1 compare:obj2 options:NSCaseInsensitiveSearch];
}];
__block NSMutableArray *codeUnits = [NSMutableArray array];
[defaultedSortedArray enumerateObjectsUsingBlock:^(NSString*  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    [codeUnits addObject:@([obj characterAtIndex:0])];
}];
NSLog(@"預設Unicode碼點值排序 %@ 對應的各個字元串的首字元碼點值是 %@", [defaultedSortedArray descriptionWithLocale:cnLocal], codeUnits);

輸出結果是

排序結果 
 ..  123  @  AA  abb  abc  μ  язык  ㊀  ㊤  一生一世  上  愛你 
 對應的各個字元串的首字元碼點值是 
 46  49  64  65  97  97  956  1103  12928  12964  19968  19978  29233 

我們常用的各種字元的碼點值範圍是:

  • 0-9 U+0030 - U0039
  • a-z U+0061 - U+007A
  • A-Z U+0041 - U+005A 具體可通過:unicode-table查詢。

UCA 預設排序

在我們前面下載的文件CLDR庫有個/common/uca/allkeys_CLDR.txt文件,它表示我們指定locale為“en”或者說是預設的排序規則。它的格式是

0000  ; [.0000.0000.0000] # <NULL>
0001  ; [.0000.0000.0000] # <START OF HEADING>
0002  ; [.0000.0000.0000] # <START OF TEXT>
0003  ; [.0000.0000.0000] # <END OF TEXT>

分號前的值表示碼點,分號後中括弧裡面的值表示UCA演算法權重,用.號來區分,Unicode字元就是按照這個規則從上到下排序。

NSLocale *enLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en"];
defaultedSortedArray = [rawArray sortedArrayUsingComparator:^NSComparisonResult(NSString*  _Nonnull obj1, NSString*  _Nonnull obj2) {
    return [obj1 compare:obj2 options:0 range:NSMakeRange(0, obj1.length) locale:enLocale];
}];
NSLog(@"預設排序規則或者指定地區為locale後的排序結果是 %@", [defaultedSortedArray descriptionWithLocale:cnLocal]);

排序結果是

預設排序規則或者指定地區為en後的排序結果是 
 ..  (ch  (en  @  123  AA  abb  abc  μ  язык  ㊀  一生一世  上  ㊤  愛你 

這種排序依次為符號,數字,英文/漢字等script charaters。

CLDR調整後的排序

在下載的CLDR文件中,有個common/bcp47/collation.xml文件,列出了可選的排序方式,有standard,pinyin, stroke(筆畫排序)等。

img排序可選方式

那如何確定各個區域語言下,該使用哪種排序規則呢,我們可以看到common/collation/文件夾下,有很多標記語言LDML文件,這些文件就是表示在不同區域語言下,採用的排序規則。

img

我們打開zh.xml,這個就是我們簡體中文的排序規則,可以看到,裡面預設採用的排序是pinyin排序,並且在開頭還寫了各個聲調字母的排序先後順序。

img

  1. 首先按照pinyin聲調的先後順序進行排序,即zh.xml底下列出的先後順序進行排序。
  2. 如果是在同一行的漢字,則按照筆畫由少到多的順序進行排序。
  3. 如果還不能區分大小,就按照kRSUnicode (偏旁索引的方式,按照康熙字典的定義)的先後順序進行排序。

假如我們指定區域為zh_CN,則對於字元串中出現的中文則排在其他語言字元串前面。其他script charater則按照allkeys_CLDR.txt的順序進行進行排序。值得註意的是,中文由於多音字,在這裡不一定能夠完全按照我們的習慣排序正確,比如“重逢(chong feng)”就沒有第一個拼音chong去排,而是按照zhong來排列的。

預設排序規則或者指定地區為zh_CN後的排序結果是 
 ..  (ch  (en  @  0124  123  艾你  愛你  産  上  ㊤  ㊀  一生一世  重逢  重要  aa  AA  abb  μ  язык 
 
 預設排序規則或者指定地區為ru_CN後的排序結果是 
 ..  (ch  (en  @  0124  123  язык  aa  AA  abb  μ  ㊀  一生一世  上  ㊤  愛你  産  艾你  重要  重逢 
 

至此,我們大致講清楚了幾種排序規則。

蘋果系統的排序

前面我們已經說了,蘋果系統的NSString排序是UCA和CLDR規則的。NSString提供了很多的排序方法,但最終,所有的都是調用了compare:options:range:locale:來進行處理,只是傳入的參數不同。可以在NSString.swift 中查看具體的實現。這麼多排序方法中,其中之一是localizedStandardCompare:, 這個方法是蘋果系統推薦的,在給用戶展示的列表數據的名字或者其他字元串進行排序時所使用的方法。我們看到,它的內部實現是

   public func localizedStandardCompare(_ string: String) -> ComparisonResult {
        return compare(string, options: [.caseInsensitive, .numeric, .widthInsensitive, .forcedOrdering], range: NSRange(location: 0, length: length), locale: Locale.current._bridgeToObjectiveC())
    }

其中用到的四個Options參數是

NSCaseInsensitiveSearch  //大小寫不敏感
NSNumericSearch //對字元串中出現的數字字元進行數字化的大小比較,比如Foo2.txt < Foo7.txt < Foo25.txt
NSWidthInsensitiveSearch //忽略寬度,按照實際表示的意思來對比,如'a' = UFF41
NSForcedOrderingSearch //強制返回Ascending或者Descending,和NSCaseInsensitiveSearch結合起來就是例如"aaa" > "AAA"

並且指定了當前的區域locale作為參數,這就相當於指定使用CLDR進行排序,如果是在手機上,這個方法的調用和系統當前的區域設置是有很大關係的,這和我們代碼中設置locale是一個道理。我們可以這樣理解,調用這個方法得到的結果和在iOS Files中文件名選擇按照名稱排序得到的結果是一樣的。在iOS中,當我們的區域設置為中國時,排序順序就是 標點符號等特殊符號>數字>中文>英文等其他

img區域設置成中文後的排序

自此,對localizedStandardCompare:的使用,大家應該比較清楚了。

數字的比較

這裡單獨把數字字元串的比較列出來,是因為一些人對這裡比較迷惑。由於localizedStandardCompare:中有使用NSNumericSearch選項,這裡簡單來說,就是假如目前兩個字元串是相等的,兩者都出現了數字,則分別從兩者種取出這段數字進行數字化來比較大小,按照數字大小排序。為了驗證這裡的邏輯,我看了下CFString.cCFStringCompareWithOptionsAndLocale這個方法的實現,這個就是compare實際調用的的比較方法。其中關於數字大小比較的代碼如下:

if (numerically && ((0 == strBuf1Len) && (str1Char <= '9') && (str1Char >= '0')) && ((0 == strBuf2Len) && (str2Char <= '9') && (str2Char >= '0'))) { // If both are not ASCII digits, then don't do numerical comparison here
        uint64_t intValue1 = 0, intValue2 = 0;  // !!! Doesn't work if numbers are > max uint64_t
        CFIndex str1NumRangeIndex = str1Index;
        CFIndex str2NumRangeIndex = str2Index;

        do {
            intValue1 = (intValue1 * 10) + (str1Char - '0');
            str1Char = CFStringGetCharacterFromInlineBuffer(&inlineBuf1, ++str1Index);
        } while ((str1Char <= '9') && (str1Char >= '0'));

        do {
            intValue2 = intValue2 * 10 + (str2Char - '0');
            str2Char = CFStringGetCharacterFromInlineBuffer(&inlineBuf2, ++str2Index);
        } while ((str2Char <= '9') && (str2Char >= '0'));

        if (intValue1 == intValue2) {
            if (forceOrdering && (kCFCompareEqualTo == compareResult) && ((str1Index - str1NumRangeIndex) != (str2Index - str2NumRangeIndex))) {
                compareResult = (((str1Index - str1NumRangeIndex) < (str2Index - str2NumRangeIndex)) ? kCFCompareLessThan : kCFCompareGreaterThan);
                numericEquivalence = true;
                forcedIndex1 = str1NumRangeIndex;
                forcedIndex2 = str2NumRangeIndex;
            }

            continue;
        } else if (intValue1 < intValue2) {
            if (freeLocale && locale) {
                CFRelease(locale);
            }
                return kCFCompareLessThan;
            } else {
                if (freeLocale && locale) {
                    CFRelease(locale);
                }
                return kCFCompareGreaterThan;
        }
    }

這段代碼的含義就是,如果兩個字元串都是以數字開始(也可能是字元串前面都相等,當前從數字部分開始比較),則取出兩個字元串的數字,按照數字大小進行對比。如果數字能夠比較出大小,則直接返回兩個字元串的大小關係,不再對後面的字元串進行對比。比如“0123aaa” 和“1bbbbbbbbb”,就直接返回“0123aaa”大於“1bbbbbbbbb”。當然,這裡取出的數字可能超出了uint64_t表示的最大值,但是這種概率很低,在我們的名稱排序中,很難遇到這麼長的數字進行比較的。明白這個規則後,大家對字元串中出現的數字在進行排序時應該比較理解了。下麵的名字排序是對著的。

img

綜述

本文主要講述由localizedStandardCompare:這個蘋果系統方法所引發的對排序規則的深入研究,簡單來說,設置中選擇區域為中國時,排序順序為 標點符號等特殊符號>數字>中文>英文等其他。中文本身是按照pinyin排序的,只是由於多音字的關係,不能夠做到100%按照中文習慣來排序,會有些無法正確排序的問題,但大體已經符合我們的習慣了。

參考

https://zh.wikipedia.org/wiki/Unicode

https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFStrings/Articles/UnicodeBasis.html

https://www.objc.io/issues/9-strings/unicode/

http://unicode.org/reports/tr10/

https://www.cnblogs.com/huahuahu/p/Unicode-zi-fu-chuan-pai-xu-gui-ze-yi-ru-he-que-din.html

https://raw.githubusercontent.com/larvit/larvitgeodata/master/cldrData/common/uca/allkeys_CLDR.txt

http://cldr.unicode.org/

相關閱讀
【每日課程推薦】機器學習實戰!快速入門線上廣告業務及CTR相應知識

此文已由作者授權騰訊雲+社區發佈,更多原文請點擊

搜索關註公眾號「雲加社區」,第一時間獲取技術乾貨,關註後回覆1024 送你一份技術課程大禮包!

海量技術實踐經驗,盡在雲加社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • plist關鍵欄位: CFBundleIdentifier:應用包名、唯一標識 CFBundleVersion:文件版本號,可以每次發版本遞增 CFBundleShortVersionString:appstore顯示版本號,每次app store發版遞增 CFBundleName:預設設置是等同於 ...
  • iOS 用KVC設置結構體 在Fundation中KVC提供的鍵值路徑只能訪問對象,不能訪問結構體。這很不面向對象。 執行下麵的語句將會報錯: 實現這個功能是很簡單的:首先NSValue的結構體信息,然後拿到關於這個結構體如何取值和賦值的信息。有了這兩條信息就可以了。 最方便的數據結構就是字典,Ke ...
  • 在最近主導的一個項目中,App端的實現使用了weex。通過近一個月的實踐,我們發現如果對於人機交互較少的App,即使較少前端經驗的人也能迅速進入開發(當然需要一定時間 才能上手weex)。在開發的時候,我們使用了weex-ui庫,但也發現其中有一些控制項沒有實現,如UISegmentControl。於 ...
  • Link Map File中文直譯為鏈接映射文件,它是在Xcode生成可執行文件的同時生成的鏈接信息文件,用於描述可執行文件的構造部分,包括了代碼段和數據段的分佈情況。 ...
  • 大家都知道,textView有一個setCompoundDrawables的方法來設置上下左右位置的圖標,當然,也可以在xml佈局文件中設置,然而問題來了,假如我們把圖標放在左邊,當我們讓TextView分多行顯示的時候,會出現一種情況,左邊的圖標並不會與第一行對齊,而是與整個textView居中對 ...
  • iOS藍牙類APP常駐後臺的實現方法,經過在蘋果開發者論壇詢問,以及查看蘋果開發者文檔,最後得出正確的方法為: 1.設置plist,藍牙許可權 2.到target-capabilities-background modes中打開use Bluetooth LE accessories選項 3.創建ce ...
  • 我有一個需求:在點擊或長按某個按鈕時,需要顯示提示,包括簡單的文字提示,還有複雜一點的圖片甚至是動態圖的提示(可能還要加上文字)。感謝各位的關註,後面我會把IOS客戶端源碼、API源碼呈獻給大家,完整的示例大家可以暫時到AppStore下載安裝“知音”。 ...
  • Window -> Organizer ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...