告訴一個不同的.NET Framework字符串駐留

.NET Framework在 實際應用中,仍是至關複雜的。咱們要向熟練的運用這一架構來服務於咱們的程序代碼中。關於.NET Framework字符串駐留的機制,對於那些瞭解它的人確定會認爲很簡單,可是我相信會有很大一部分人對它存在迷惑。在開始關於字符串的駐留以前,先給 出一個有趣的Sample: 數據結構

Code Snip: 架構

  1. static void Main(string[] args)   
  2. {   
  3. string str1 = "ABCD1234";   
  4. string str2 = "ABCD1234";  
  5. string str3 = "ABCD";   
  6. string str4 = "1234";   
  7. string str5 = "ABCD" + "1234";   
  8. string str6 = "ABCD" + str4;   
  9. string str7 = str3 + str4;   
  10. Console.WriteLine("string str1 = 
    \"ABCD1234\";");  
  11. Console.WriteLine("string str2 = 
    \"ABCD1234\";"); 
  12. Console.WriteLine("string str3 = 
    \"ABCD\";"); 
  13. Console.WriteLine("string str4 = 
    \"1234\";"); 
  14. Console.WriteLine("string str5 = 
    \"ABCD\" + \"1234\";"); 
  15. Console.WriteLine("string str6 = 
    \"ABCD\" + str4;"); 
  16. Console.WriteLine("string str7 = 
    str3 + str4;"); 
  17. Console.WriteLine("\nobject.Reference
    Equals(str1, str2) = {0}", object.
    ReferenceEquals(str1, str2)); 
  18. Console.WriteLine("object.ReferenceEquals
    (str1, \"ABCD1234\") = {0}", object.
    ReferenceEquals(str1, "ABCD1234"));   
  19. Console.WriteLine("\nobject.Reference
    Equals(str1, str5) = {0}", object.
    ReferenceEquals(str1, str5)); 
  20. Console.WriteLine("object.Reference
    Equals(str1, str6) = {0}", object.
    ReferenceEquals(str1, str6)); 
  21. Console.WriteLine("object.ReferenceEquals
    (str1, str7) = {0}", object.ReferenceEquals
    (str1, str7)); 
  22. Console.WriteLine("\nobject.ReferenceEquals
    (str1, string.Intern(str6)) = {0}", object.
    ReferenceEquals(str1, string.Intern(str6)));   
  23. Console.WriteLine("object.ReferenceEquals
    (str1, string.Intern(str7)) = {0}", object.
    ReferenceEquals(str1, string.Intern(str7)));   

接下來咱們來逐句地分析這段.NET Framework字符串駐留代碼: 函數

首先咱們建立了兩個徹底相同的字符串(ABCD1234),並將他們分別賦予了兩個字符創變量——str1和str2。而後把它們傳給了 object.ReferenceEquals。咱們知道object.ReferenceEquals是用於肯定兩個變量是否具備相同的引用——換句話 說,當兩個變量引用的是同一塊託管推的內存快的時候,返回True,不然返回False。 性能

令咱們感到奇怪的是,當咱們分別建立的引用類型兩個變量——string是引用類型。照理說CLR會在託管堆(Managed Heap)中爲它們分配兩段內存快,他們不可能具備相同的引用纔對,可是爲何object.ReferenceEquals 方法會返回True呢。而對於第二個比較——一個字符串變量和一個和他具備相同內容的字符串("ABCD1234";)直接進行比較,按照咱們對CLR內 存的分配的通常理解,應該是CLR首先會在託管堆中爲這段字符串("ABCD1234")分配內存快,而後把相對應的引用傳遞給 object.ReferenceEquals方法(因爲分配在託管堆的這段字符串並無被任何變量引用,因此當垃圾回收的時候會被回收掉),因此不管如 何也不該該返回True。 編碼

咱們先把問題留到最後,接着分析咱們的Sample。上面們對字符串變量之間以及變量與字符串之間進行了比較,若是咱們對一個字符串變量和一個動態建立的字符串(經過+Operator把兩個字符串鏈接起來)進行比較,結果又會如何呢?咱們來看看下面的僞代碼演示: spa

在上面的.NET Framework字符串駐留例子中,咱們用三種不一樣的方式建立了3 個字符串變量(str5,str6,str7)——string+string;string+variable;variable+variable。 而後分別和咱們已經建立的、和它們具備相同字符串「值」的變量(str1)做比較。一樣令咱們感到奇怪的是第一個返回True,然後兩個則爲False。 帶着這些疑惑咱們來看看對於string這一特殊的類型說採用的特殊的使用機制。 htm

1. System.String雖然是一個引用類型,可是它具備其自身的特殊性。咱們最容易想到的是它建立的特殊性——通常的對象在建立的時候須要經過new 關鍵字調用對應的構造函數來實現;而建立一段string不須要這麼作——咱們只須要把對應的字符換賦給給對應的字符串變量就能夠了。 對象

之因此存在着這種差別,是由於他們在建立過程當中使用的IL指令時不一樣的——通常的引用對象的建立是經過newobj這樣一個IL指令來實現的,而創 建一個字符串變量的IL指令則是ldstr (load string)。(象C#,VB.NET這樣的語言畢竟是高級語言,進行了高度的抽象,站在這樣的角度分析問題每每不可以看到其實質,因此有時候咱們把應 該從交底層上面找突破口——好比分析IL,Metadata…); 事件

2.因爲String是咱們作到頻率最高的一種類型,CLR考慮性能的提高和內存節約上,對於相同的字符串,通常不會爲他們分別分配內存塊,相反 地,他們會共享一塊內存。CLR實際上採用這個的機制來實現的:CLR內部維護着一塊特殊的數據結構——咱們能夠把它當作是一個Hash table,這個Hash table維護者大部分建立的string(我這裏沒有說所有,由於有特例)。這個Hash table的Key對應的相應的string自己,而Value則是分配給這個string的內存塊的引用。 ip

當CLR初始化的時候建立這個Hash table。通常地,在程序運行過程當中,若是須要的建立一個string,CLR會根據這個string的Hash Code試着在Hash table中找這個相同的string,若是找到,則直接把找到的string的地址賦給相應的變量,若是沒有則在託管堆中建立一個string,CLR 會先在managed heap中建立該strng,並在Hash table中建立一個Key-Value Pair——Key爲這個string自己,Value位這個新建立的string的內存地址,這個地址最重被賦給響應的變量。這樣咱們就能解釋上 面.NET Framework字符串駐留的疑問了。

當建立str1的時候,CLR如今咱們上面提到的Hash table中找「ABCD1234」這樣的一個string,沒有找到,則在託管堆中爲這個string分配一塊內存,而後在Hash table爲該string添加一個Key-Value Pair。接着建立str2,CLR仍然會在Hash table找ABCD1234這樣的一個string,這回它會找到咱們新建立的這個Entry,因此這個Key-Value Pair中Value(string的地址)會賦給str2。由於str1和str2 具備相同的引用,因此調用object.ReferenceEquals返回True。同理當咱們對str1和"ABCD1234"進行比較的時候, str1直接傳入該方法,放傳入"ABCD1234"這個字符串的時候,CLR一樣會在Hash table找ABCD1234這樣的一個string,相同的Entry被找到,這個Entry(Key-Value Pair)的Value(string的地址)被傳到object.ReferenceEquals,因此他們仍然相同的引用,結果返回True。

3.並不是全部的狀況下.NET Framework字符串駐留都會起做用。對於對一個動態建立的字符串(好比string+variable;variable+variable),這 種駐留機制便不會起做用。由於對於這樣的字符串,是不會被添加到內部的Hash table中的。可是對於string+string則不一樣,由於當這樣的語句被編譯成IL的時候,編譯器是先把結構計算出來,而後再調用ldstr指令 ——而對於string+variable;variable+variable這種狀況,所對應的IL指令是Concat。因此對於string+ string字符串的駐留仍然有效。

因此如今咱們就能夠解釋第二個疑問了。

雖然對於對一個動態建立的字符串(好比 string+variable;variable+variable),.NET Framework字符串駐留機制便不會起做用。可是咱們能夠手工的啓用駐留機制——那就是調用定義的 System.String中的靜態方法Intern。這個方法接受一個字符串做爲他的輸入參數,返回的通過駐留處理的string。他的實現機制是:如 果能在內部的Hash Table中找到傳入的string,則返回對應的string引用,不然就在Hash Table添加該string對應的Entry,並返回string的引用。因此下面的代碼就不難解釋了。

相關文章
相關標籤/搜索