走鋼絲的人,在刺激中體驗快感。帶著問題思考,在問題上迸發火花。
或者給問題以答案,或者給答案以問題,你可能永遠無法看清全部,但是總能從一點突破很多。事實的關鍵就在於面對問題,我該如何思考?
String Interning(字符串駐留)就是這樣一個值得思考的話題,帶著問題思考,我們至少要理清以下幾個問題:
什麼是string?
什麼是字符串駐留?
字符串駐留的運行機制及執行過程?
字符串駐留的其他問題?
帶著幾個問號,你必須知道的.NET,繼續更多體驗。
1 帶著問題?
帶著問題思考,是技術探索的最佳實踐, 每當我收到很多朋友來函探討技術的問題,總能給我很多的技術思索和驚喜,今天我們的話題就是由一個朋友的來函開始的,你可以通過鏈接打開KiMoGiGi在To 王濤 的問題一文中精彩絕倫的思考和探討,帶著他的提問,引著我的思考,完成本文對string的一點點探討。
首先,本文也無一例外的從8個測試開始,也希望讀者能沿著這幾個簡單的示例來思考答案。如果對此包含熱情,不妨可以試試,你開始了嗎?
// Release : code01, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s1 = "abc";
Console.WriteLine(string.IsInterned(s1) ?? "null");
}
這是個簡單的例題,可以很快給出答案。
// Release : code02, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s1 = "ab";
s1 += "c";
Console.WriteLine(string.IsInterned(s1) ?? "null");
}
稍加修改,這回的答案又該如何分析,我們繼續。
// Release : code03, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s1 = "abc";
string s2 = "ab";
s2 += "c";
string s3 = "ab";
Console.WriteLine(string.IsInterned(s1) ?? "null");
Console.WriteLine(string.IsInterned(s2) ?? "null");
Console.WriteLine(string.IsInterned(s3) ?? "null");
}
如果上述執行過程,你能很快給出答案,那麼恭喜了,第一關看來不是那麼費勁,我們接著思考,繼續第二關:
// Release : code04, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s1 = "abc";
string s2 = "ab";
string s3 = s2 + "c";
Console.WriteLine(string.IsInterned(s3) ?? "null");
}
還有一個,我們繼續
// Release : code05, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
string s1 = "abc";
}
你的答案怎麼是?我們還是接著迎接挑戰:
// Release : code06, 2008/08/20
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
string s1 = GetStr();
}
private static string GetStr()
{
return "abc";
}
這是第二關了,你的思考肯定還在繼續,我們第三關也呼之欲出:
// Release : code07, 2008/08/20
// Author : Anytao, http://www.anytao.com
public const string s1 = "abc";
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
}
.csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }
最後一個,沖出藩籬:
// Release : code08, 2008/08/20
// Author : Anytao, http://www.anytao.com
public static string s1 = "abc";
static void Main()
{
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
}
過關斬將,三輪PK,是英雄比高。不管怎樣,你的答案和思考,肯定會讓大家對string刮目相看,是否和你一直以來的認識統一呢?在此感謝KiMoGiGi 給我的啟示。有了問題,我們更需要的是思考、探討和反思。
2 欲求思考
欲求思考,則從基本開始,對於理解整個string intern機制是大有裨益的,因此深入的第一步就從基本概念開始。隨著我們分析的層層深入,就會發現看似曲折的結果,原來不過如此而已,這正是技術探求的最佳方式。
什麼是string
什麼是string呢,提起這個問題,我想下面的圖例可以給出一點啟示:
string在本質上就是一連串的有順序的字符集合。
簡單的說,string就是char[],而在.NET中string頭一回具有了類的概念,暗合了.NET一切皆為對象的大一統格局。回歸本質,我們重新審視如此另類而多彩的string,你會不禁明白,string本質上就是一個16位Unicode字符數組。打開string的Disassemble代碼,我們可直擊其本質:
[Serializable, ComVisible(true)]
public sealed class String : IComparable, ICloneable, IConvertible, IComparable<string>, IEnumerable<char>, IEnumerable, IEquatable<string>
{
}
結合string的定義,我們可以看出其基本的特性主要包括:
引用類型,string本質上是引用類型,相關內容參考《你必須知道的.NET》 對值類型和引用類型的討論。
字符串恆等性。
字符串駐留性,本文的研究重點。
密封性,由sealed關鍵字可見,sealed特性為實現字符串恆等性和字符串駐留機制,提供了基礎保證,具體的原因參見《你必須知道的.NET》 關於string的相關論述。
關於這些特性並非本文關注的熱點,還有大量的命題值得我們關注,總結起來還可包括:
字符串比較:以等價規則而非恆等規則進行比較。
常用方法:Trim()、ToLow()、Replace()、Split()、PadRight()、SubString()和Join()
格式化。
轉移字符。
StringBuilder,另一個重要的話題。
Encoding,編碼。
Culture & Internationalization,語言文化。
overloads == ,==重載。
由此可知,string真是一個豐富而多彩的技術倉庫,飽含了.NET技術中很多精髓與技巧,我們不可能在本文中盡述其然,更多的論述和分析可以參考以下信息:
接下來,本文的主題閃亮登場。
什麼是字符串駐留(String Interning)
回歸經典,我們首先給出MSDN對於字符串駐留的一點討論:
公共語言運行庫通過維護一個表來存放字符串,該表稱為拘留池,它包含程序中以編程方式聲明或創建的每個唯一的字符串的一個引用。因此,具有特定值的字符串的實例在系統中只有一個。
例如,如果將同一字符串分配給幾個變量,運行庫就會從拘留池中檢索對該字符串的相同引用,並將它分配給各個變量。
之所以,將string這個熟悉的命題拿出來造輪子,並不是再造個輪子自己陶醉。關於string的輪子,實在太多了,而且個個不順眼,它就像編程的精靈,四處可見隨處都有。string是如此的重要,以至於CLR必須以特殊的方式來實現對string類型的管理、存取和布局,在這些復雜的特殊表象中,字符串駐留機制是string特殊性的集中體現,它的基本原理可以概括為:
CLR維護一個類似於哈希表的內部結構,用於維護對於字符串的統一管理。
但JIT編譯時,CLR首先查找哈希表,如果沒有找到匹配的字符串記錄,則在托管堆中創建新的string實例,並為哈希表添加一個鍵值對記錄;下一次查找相同string時,則只返回該記錄的值給第二次創建的string對象。
通過這種方式,字符串駐留機制有效實現了對string的池管理,節省了大量的內存空間。
我們可以從code01盡情領略字符串駐留機制的基本原理,然而關於字符串駐留,並不是幾句簡單原理就能全面概括的問題。
注意:動態創建的字符串是不執行字符串駐留機制的,例如通過:
// Release : code09, 2008/08/25
// Author : Anytao, http://www.anytao.com
static void Main()
{
string s1 = "abc";
string s2 = "ab";
string s3 = s2 + "c";
Console.WriteLine(ReferenceEquals(s1, s3));
}
但是對於“動態”二字的把握並非一件簡單的事情,什麼情況下執行字符串駐留,而什麼時候不會執行字符串駐留,在.NET spec中我並沒有找出足夠精確的正解來闡釋這個問題,例如code06示例中,string s1 = GetStr(); 的位置很大程度上決定了是否執行字符串駐留條件,從code06示例的結果可見,string.IsInterned(s2)並未收獲返回“abc”的預期結果,而下面的示例則又給出意想不到的答案:
// Release : code10, 2008/08/25
// Author : Anytao, http://www.anytao.com
static void Main()
{
//在這個位置返回abc
string s1 = GetStr();
string s2 = "ab";
s2 += "c";
Console.WriteLine(string.IsInterned(s2) ?? "null");
}
private static string GetStr()
{
return "abc";
}
對比code06和code10,我們發現不同的只是string s1 = GetStr(); 的位置,而結果卻大相徑庭。
為什麼?
位置的不同而導致觸發字符串駐留機制是否執行的條件不同,這正是我們通過實例反向驗證的最佳體現。那麼,你的思考呢?
這些看似熟悉的問題,其實都值得推敲,本文沒有太多的精力兼顧所有,只能在邊緣之余探討一下遺留在字符串駐留機制中一些並不是很清楚的問題,算是對字符串駐留機制的進一步探討,主要包括:
CLR的加載過程。
intern pool在什麼時候創建,如何創建?
駐留機制的簡述。
方法的調用過程。
介紹IsInterned和Intern方法。
string intern的失效和弊端。
以解決本文開題的幾個典型的問題,同時順便解答KiMoGiGi在To 王濤 的問題中提出的問題。下面我們一一揭開這些問題的神秘面紗。
作為字符串駐留機制探討的第一篇,我們從問題出發引出對於字符串駐留的定義和概念,在未來的篇章中除了說明上述問題之外,我們還將力圖解釋開篇8個示例的個中結果,並對可能的情況和問題進行一些對比性的推敲。
事實上,由string intern而引發的技術論題,還有很多值得我們品味和玩味,這也正是這個本文及其後續篇章力圖做出的努力。