最近一段時間一直在重看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*)(chPtr2 + 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類型的比較方法,也留下了一些疑問,也希望可以得到各位的解答。