字串比較中NSNumericSearch選項的工作原理
原文地址:蘋果梨的部落格
相信研究過怎麼在 ObjC 中進行版本字串比對的朋友,大多都看過這一篇 StackOverflow 的問答:
Compare version numbers in Objective-C
裡面提到的 [versionStrA compare:versionStrB options:NSNumericSearch]
的方案應該是最優雅的方案了。
但是不理解這個 NSNumericSearch 的具體工作原理就去盲目使用是危險的,今天我就來研究下它的具體工作原理。
官方文件
參照官方文件裡的說明:
Numbers within strings are compared using numeric value, that is, Name2.txt < Name7.txt < Name25.txt. Numeric comparison only applies to the numerals in the string, not other characters that would have meaning in a numeric representation such as a negative sign, a comma, or a decimal point. 複製程式碼
粗略的直譯一下:
在字串中的數字將被用數值進行比較,就是說,Name2.txt < Name7.txt < Name25.txt。 數值比較僅僅對字串中的純數字(0-9)生效,而不對其它在數字表達中含有意義的字元生效, 例如負號,逗號或小數點。 複製程式碼
這段說明略有歧義,導致很多人第一次看的時候被繞暈。例如剛剛那篇 StackOverflow 問答裡的 dooleyo 就理解成 "1.2.3"
和 "1.1.12"
進行比較時,會拋棄所有非數字的字元變成 123
和 1112
進行比較,最後得到 "1.2.3" < "1.1.12"
的結論。問答裡不少其它朋友也有類似的想法。
探求真相
真相只有經過實驗才能得到,所以寫了一些測試程式碼來試一下具體的結果:
- (void)testExample { [self compareString:@"1.2.3" andString:@"1.1.12"]; [self compareString:@"1.8" andString:@"1.7.2.3.55"]; [self compareString:@"1.44" andString:@"1.5"]; [self compareString:@"7.4.1" andString:@"7.5"]; } - (void)compareString:(NSString *)stringA andString:(NSString *)stringB { NSComparisonResult result = [stringA compare:stringB options:NSNumericSearch]; switch (result) { case NSOrderedDescending: NSLog(@"%@ > %@", stringA, stringB); break; case NSOrderedAscending: NSLog(@"%@ < %@", stringA, stringB); break; default: NSLog(@"%@ = %@", stringA, stringB); break; } } 複製程式碼
得到的結果是:
1.2.3 > 1.1.12 1.8 > 1.7.2.3.55 1.44 > 1.5 7.4.1 < 7.5 複製程式碼
看上去結果都是正確的,那麼看來 NSNumericSearch
並不是粗暴的去掉所有非數字字元後進行數值比對。
結合原回答裡答主說的一句話: keeping in mind that "1" < "1.0" < "1.0.0"
,忽然想到了一些什麼,繼續進行下一步的實驗,得到的結果如下:
1 < 1.0 1.0 < 1.0.0 a10 < b2 a2 < b10 c10 > b2 c2 > b10 2a < 10b 10a > 2b 2c < 10b 10c > 2b 複製程式碼
大家看到這裡應該可以猜到官方文件的意思是什麼了,其實文件的意思是整個字串 非數字的部分仍然進行常規的字元比較邏輯,只有在遇到數字的時候會把連續的數字轉換成數值再進行比對 。具體的比較過程示例參照下圖:

這時候一些奇特的比對結果就可以解釋明白了,比如使用這種比較模式會得出 "01" = "1"
。
繼續深入
那麼還剩下一個問題,如果和數字字元比較的是非數字字元,會怎麼樣?我們可以挑一些 ASCII 碼在數字字元周圍的字元進行試驗,結果如下:
a/c < a100c a:c > a100c a/c < a1c a:c > a1c 複製程式碼
注意這裡出現的部分字元 ASCII 碼為:
/ = 47 0 = 48 1 = 49 ... 9 = 57 : = 58 複製程式碼
可以看出現了比較的字元一邊是數字,一邊是非數字時,是按照常規的 ASCII 碼進行比對的。
總結
那麼分析到這裡就基本結束了,剩下的一些場景類推一下都很容易理解。
其實在各大 OS 裡的檔案系統下檔案排序用的就是這種比較方法,一開始沒有想到這點所以理解上繞了一些彎路。
用 NSNumericSearch
來進行版本字串的比對也是十分有效的,不是特殊需要的話就再也不用傻傻的自己分割字串再分段比較啦。