總結字符串比較函數
最近一段時間一直在重看CLR via C# , 這次把字符串比較的函數總結下。
1.Compare和CompareTo大PK
首先是我們最常用的String.Compare和CompareTo實例方法,先來看看這兩個方法:
我們通過這個可以直觀地看到,String的靜態方法要比CompareTo多出好多的方法重載,其實這也是兩者的最大區別,也就是說String.Compare有著更多的功能選項供我們控制。
其中主要包含著三個方面:
A. 文化信息
B. CompareOptions
C. 比較的開始和結束位置
對于文化信息,我們可以看下Compare的反編譯結果:
對于Compare來說,他會通過傳遞進來的文化信息來調用對應的比較。
而CompareTo則是:
CompareTo則會調用與當前線程相關聯的文化信息。
對于文化信息來說,還有著這樣一個枚舉選項:StringComparison:
下面讓我們來看下StringComparison枚舉:
對于該枚舉,共有如上六個枚舉值。該枚舉主要對應著當前的文化信息,大小寫,以及排序規則。
這就意味著,如果我們進行國際化的時候,字符串比較必須使用String.Compare靜態方法。
下面來看下CompareOptions:
最后看下含開始和結束位置的String.Compare方法:
方法本身很簡單,而方法鏈的最末端使用的是:
一個內部比較字符串的非托管方法,而方法的具體內容,我無從而知,但是可以明確的是,這一定是一個高效的比較算法。
因此,當我們每次SubString的時候,當我們ToLower的時候,我們不妨都在這里使用String.Compare,是不是為我們節省了很多空間,提高了很大效率呢?
因此,我在這里建議,如果可能,我們盡可能地使用String.Compare方法來代替CompareTo方法!
2. 被遺忘的CompareOrdinal
讓我們先來看下CompareOrdinal的源碼:
private static unsafe int CompareOrdinalHelper(string strA, string strB) { int num = Math.Min(strA.Length, strB.Length); int num2 = -1; fixed (char* chRef = &strA.m_firstChar) { fixed (char* chRef2 = &strB.m_firstChar) { char* chPtr = chRef; char* chPtr2 = chRef2; while (num >= 10) { if (*(((int*)chPtr)) != *(((int*)chPtr2))) { num2 = 0; break; } if (*(((int*)(chPtr + 2))) != *(((int*)(chPtr2 + 2)))) { num2 = 2; break; } if (*(((int*)(chPtr + 4))) != *(((int*)(chPtr2 + 4)))) { num2 = 4; break; } if (*(((int*)(chPtr + 6))) != *(((int*)(chPtr2 + 6)))) { num2 = 6; break; } if (*(((int*)(chPtr + 8))) != *(((int*)(chPtr2 + 8)))) { num2 = 8; break; } chPtr += 10; chPtr2 += 10; num -= 10; } if (num2 == -1) { goto Label_00F1; } chPtr += num2; chPtr2 += num2; int num3 = chPtr[0] - chPtr2[0]; if (num3 != 0) { return num3; } return (chPtr[1] - chPtr2[1]); Label_00D7: if (*(((int*)chPtr)) != *(((int*)chPtr2))) { goto Label_00F5; } chPtr += 2; chPtr2 += 2; num -= 2; Label_00F1: if (num > 0) { goto Label_00D7; } Label_00F5: if (num > 0) { int num4 = chPtr[0] - chPtr2[0]; if (num4 != 0) { return num4; } return (chPtr[1] - chPtr2[1]); } return (strA.Length - strB.Length); } } }
方法很長,但是很簡單,即使是Reflector 出來的變量名很BT,但是我們也可以大致看個究竟。
他是將整個字符串每5個字符(10個字節)分成一組,然后逐個比較,找到第一個不相同的ASCII碼后退出循環。并且求出兩者的ASCII碼的差。不過我很費解的是微軟為什么要把這個實現的如此麻煩。只能等到周一再求解了。
但是在CLR via C#上有這樣的話:這個方法比其他方法都要快。我想應該是有一定道理的吧。
所以當我們比較大小的時候,盡量使用CompareOrdinal方法。
3. 常用的Equals方法
先來看Equals實例方法:
方法會首先進行合法性判斷,然后比較兩者是否指向同一塊引用,接下來調用EqualsHelper方法(不清楚微軟為什么很沉迷于XXXHelper這個命名,難道XXXHelper這個名詞不應該是一個類名么?)
private static unsafe bool EqualsHelper(string strA, string strB) { int length = strA.Length; if (length != strB.Length) { return false; } fixed (char* chRef = &strA.m_firstChar) { fixed (char* chRef2 = &strB.m_firstChar) { char* chPtr = chRef; char* chPtr2 = chRef2; while (length >= 10) { if ((((*(((int*)chPtr)) != *(((int*)chPtr2))) || (*(((int*)(chPtr + 2))) != *(( (int*)(chPtr2 + 2))))) || ((*(((int*)(chPtr + 4))) != *(((int*)(chPtr2 + 4)))) || (*(((int *)(chPtr + 6))) != *(((int*)(chPtr2 + 6)))))) || (*(((int*)(chPtr + 8))) != *(((int*)(chPt r2 + 8))))) { break; } chPtr += 10; chPtr2 += 10; length -= 10; } while (length > 0) { if (*(((int*)chPtr)) != *(((int*)chPtr2))) { break; } chPtr += 2; chPtr2 += 2; length -= 2; } return (length <= 0); } } }
迷糊了,又是這樣的算法,我實在不了解10字節究竟有什么奧秘,周一如果問到答案會對其進行解釋。
然而,值得肯定的是,由于是非安全代碼的比較,所以效率要比我們用安全代碼高得多。
接下來看看Equals靜態方法:
(關于==的運算符重載之前有誤,下文會解釋清楚)
4. 總結
本文主要介紹了String類型的比較方法,也留下了一些疑問,也希望可以得到各位的解答。