.NET Framework在 實際應用中,仍是至關複雜的。咱們要向熟練的運用這一架構來服務於咱們的程序代碼中。關於.NET Framework字符串駐留的機制,對於那些瞭解它的人確定會認爲很簡單,可是我相信會有很大一部分人對它存在迷惑。在開始關於字符串的駐留以前,先給 出一個有趣的Sample: 數據結構
Code Snip: 架構
接下來咱們來逐句地分析這段.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的引用。因此下面的代碼就不難解釋了。