2012-08-17 25 views
11

在下面的代碼中,我檢查對象引用的相等性。String Interning

string x = "Some Text"; 
string y = "Some Other Text"; 
string z = "Some Text"; 

Console.WriteLine(object.ReferenceEquals(x, y)); // False 
Console.WriteLine(object.ReferenceEquals(x, z)); // True 
Console.WriteLine(object.ReferenceEquals(y, z)); // False 

y = "Some Text"; 

Console.WriteLine(object.ReferenceEquals(x, y)); // True 
Console.WriteLine(object.ReferenceEquals(x, z)); // True 
Console.WriteLine(object.ReferenceEquals(y, z)); // True 

這裏:

  • xz是指相同的對象;我可以說,x是interned和z是用於taht版本。那麼,我不確定這一點;如果我錯了,請糾正我。
  • 我通過分配它的值相同爲x改變的y值。我認爲這會在這裏創造一個新的對象;但我錯了,它使用了相同的參考。

我的問題是:

  • 是否.net使用string interns爲每一個我用字符串?
  • 如果是這樣,是不是傷害表現?
  • 如果不是,上面的例子中引用如何變得相同?
+0

對[字符串實習生]很好的閱讀(http://blogs.msdn.com/b/ericlippert/archive/2009/09/28/string-interning-and-string-empty.aspx) – V4Vendetta 2012-08-17 10:27:17

回答

14

是的,在編譯器字符串常量表達式與ldstr處理,這保證實習(經由MSDN):

公共語言基礎結構(CLI)保證兩個ldstr指令的結果指的是兩個元數據具有相同字符序列的令牌精確地返回相同的字符串對象(稱爲「字符串入門」的過程)。

這不是字符串;在你的代碼中是常量字符串表達式。例如:

string s = "abc" + "def"; 

只有1字符串表達式 - 該IL將是「ABCDEF」(編譯器可以計算由表達式)一個ldstr。

這不會影響性能。在運行時產生的

字符串不會自動實習,例如:

int i = GetValue(); 
string s = "abc" + i; 

在這裏, 「ABC」 被扣留,但 「abc8」 則不是。還要注意的是:

char[] chars = {'a','b','c'}; 
string s = new string(chars); 
string t = "abc"; 

注意st是不同的引用(文字(分配給t)被拘留,但新的字符串(分配給s)不是)。

+1

難道你不知道意思是't'被攔截,但's'(新字符串)不是? – Kevin 2013-11-17 03:39:31

+0

@Kevin Thabks,澄清 – 2013-11-17 07:48:19

1

字符串文字自動扣留。

編程方式創建的字符串將不會被默認扣留(也不將用戶輸入字符串)。

在上面,「一些文本」和「其他一些文本」都被扣留因爲你正在使用的文字在這些地方,你看到的是,實習的版本是引用的一個。

在你的代碼,如果您有:

string.Format("{0} {1}", "Some", "Text") 

你會看到,返回的引用是不一樣的其他文字。

3

待辦事項es .net對每個使用的字符串都使用字符串實習生?

不,但它確實將它用於編譯時知道的那些字符串,因爲它們是代碼中的常量。

string x = "abc"; //interned 
string y = "ab" + "c"; //interned as the same string because the 
         //compiler can work out that it's the same as 
         //y = "abc" at compile time so there's no need 
         //to do that concatenation at run-time. There's 
         //also no need for "ab" or "c" to exist in your 
         //compiled application at all. 
string z = new StreamReader(new FileStream(@"C:\myfile.text")).ReadToEnd(); 
         //z isn't interned because it isn't known at compile 
         //time. Note that @"C:\myfile.text" is interned because 
         //while we don't have a variable we can access it by 
         //it is a string in the code. 

如果是這樣,是不是傷害的表現?

不,這有助於提高性能:

第一:所有這些字符串將要在應用程序的內存某處。實習意味着 我們沒有不必要的副本,所以我們使用較少的內存。其次:它使得我們所知道的字符串比較來自只是超快的字符串。第三:這並沒有多大提升,但是其他比較的提升確實如此。考慮一下內置比較器中存在的代碼:

public override int Compare(string x, string y) 
{ 
    if (object.ReferenceEquals(x, y)) 
    { 
     return 0; 
    } 
    if (x == null) 
    { 
     return -1; 
    } 
    if (y == null) 
    { 
     return 1; 
    } 
    return this._compareInfo.Compare(x, y, this._ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None); 
} 

這是爲了排序,但同樣適用於等式/不等式檢查。要檢查兩個字符串是否相等,或者要求它們按順序排列,需要我們執行O(n)操作,其中n與字符串的長度成比例(即使在可以完成一些跳過和聰明操作的情況下,它仍然是成比例的) 。對於長字符串,這可能會很慢,並且比較字符串是很多應用程序在很多情況下都會執行的操作 - 這是提高速度的好地方。這對於平等情況來說也是最慢的(因爲我們發現差異的時候我們可以返回一個值,但是必須完全檢查相同的字符串)。

即使您重新定義「等於」意味着什麼(區分大小寫,不區分大小寫,不同文化 - 一切仍然等於本身,並且如果您創建了不符合要求的覆蓋範圍,有一個錯誤)。所有東西總是以相同的點排列。這意味着兩件事:

  1. 我們總是可以考慮與自己相同的東西,而無需做任何更多的工作。
  2. 我們總是可以給出一個比較值0,用於比較某件事情和沒有任何工作。

因此上述上這種情況下捷徑的代碼,而無需做更復雜和昂貴的比較。也沒有下跌,因爲如果我們不覆蓋這種情況下,我們必須添加一個測試,以便兩個值均通過null

現在,碰巧某些算法的工作方式自然而然地比較了一些東西,所以它總是值得去做。但是,字符串實習會增加兩個字符串的時間差異(例如,在問題開始時的xz)實際上是相同的,所以它會增加快捷方式對我們的工作頻率。

大多數時候這是一個很小的優化,但是我們可以免費獲得它,我們經常得到它,擁有它是非常棒的。從這個實用的東西 - 如果你正在寫EqualsCompare考慮你是否也應該使用這個捷徑。

那麼一個相關的問題是「我應該實習一切嗎?」我們不得不考慮編譯字符串沒有的缺點。實習並不浪費編譯字符串,因爲他們必須在某個地方。但是,如果你從文件中讀取一個字符串,實施它,然後再也不用它,它將會持續很長時間,這是浪費。如果你一直這樣做,你可能會削弱你的記憶力。

讓我們想象一下,您經常閱讀一堆包含某些標識符的項目。您經常使用這些標識符將項目與其他來源的數據進行匹配。有一小部分標識符可以看到(比如只有幾百個可能的值)。然後,因爲平等檢查是這些字符串的全部內容,並且沒有太多實際情況,因此實習(在讀入的數據和您與之比較的數據 - 其他方面都沒有意義)將成爲一場勝利。

或者,假設有幾千個這樣的對象,我們匹配它的數據總是被緩存在內存中 - 這意味着這些字符串總是會在內存中的任何地方,所以實習成爲一個無需思考的工具贏得。 (除非有很多「未找到」結果的可能性 - 實習這些標識符只是爲了找不到匹配就是輸)。

最後,相同的基本技術可以做不同的處理。例如XmlReader存儲字符串,它在NameTable中比較的行爲類似於私人實習生池,但可以在完成時收集整個事情。您也可以將該技術應用於任何參考類型,這些參考類型在合併時不會更改(保證不變的最佳方法是使其不變,以便在任何時候都不會更改)。將這種技術用於具有大量重複的非常大的集合可以大量減少內存使用(我的最大節省至少爲16GB--它可能更多,但服務器在應用該技術之前在該點附近保持崩潰)和/或速度比較。