一、string是特殊的引用型別
眾所周知,string是引用型別。為什麼string是引用型別,最簡單的方法,f12轉到string的定義。顯而易見,string的本質是類,字串儲存在堆中,而string作為關鍵字,是String的別名。
然而,例項一個string物件不需要使用 new 關鍵字,而是直接使用字面值字串,如:
string str= "Hello";
Console.WriteLine(str);
Console.ReadKey();
用ILSpy檢視上述的程式碼IL,未出現預期的IL指令newobj,只有一個特殊的ldstr指令,CLR使用一種特殊的方式構造string物件。
當然,String類也提供了不同的建構函式,可使用new構造字串,如:
試著用new構造一個string物件,檢視其產生的IL,驚喜的發現new構造的字串使用IL指令newobj。
string str= new string(new char[] { 'H', 'e', 'l', 'l', 'o' });
二、string是不可變的
string物件最為重要的一個特徵:字串一旦建立便是恆定不變,字串一經建立便不能更改,不能邊長、變短或修改字元。string物件發生修改時,實際上不會改變原來的值,而是在託管堆中重新建立一個新的字串,將新的引用地址傳遞給string物件,如下圖程式碼和託管堆變化示例
string str= "Hello";
str = "你好";
由上圖可知,每次對字串進行操作,都需要在託管堆中重新分配一個新的空間地址來儲存字串,如果頻繁操作某一個string物件字串,會在堆上建立大量的物件,從而影響效能。如果需要頻繁操作字串,推薦使用StringBuilder。下面是string和StringBuilder簡單的對比:
string str= string.Empty;
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10000; i++)
{
str += "Hello World";
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
watch.Restart();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Append("Hello World");
}
watch.Stop();
Console.WriteLine(watch.Elapsed);
三、字串駐留
字串是我們十分頻繁使用的型別,由於字串是不可變的特性,會創造大量的物件,因而有了字串駐留。先看看下面的例子:
string str1 = "Hello";
string str2 = "Hello";
Console.WriteLine(object.ReferenceEquals(str1,str2));
輸出:True。也就是說,str1和str2這兩個string物件都引用了同一個記憶體地址
接下來繼續擴充示例:
string str1 = "Hello";
string str2 = "Hello";
string str3 = " LiMing";
string str4 = "Hello LiMing";
string str5 = "Hello" + " LiMing";
string str6 = str3 + " LiMing"; ;
string str7 = str3 + str4;
Console.WriteLine(object.ReferenceEquals(str1,str2));
Console.WriteLine(object.ReferenceEquals(str4, str5));
Console.WriteLine(object.ReferenceEquals(str4, str6));
Console.WriteLine(object.ReferenceEquals(str4, str7));
從上面的例子不難發現,並不是所有相等的字串的地址都是一樣的。實際上,在CLR內部維護了一個雜湊表,在這個雜湊表中,key時字串,value是託管堆中的地址。在程式執行中,CLR將字面值(形如str1)的字串,都會通過駐留機制,將這些字串進行維護到雜湊表中。當s建立str1時,會先判斷是否在雜湊表中有相同的字串,如果沒有,則建立一個新的物件;當建立str2時,雜湊表中已經有相同的字串時,直接返回已有的引用地址給str2。那為什麼str6、str7的引用地址和str4不相同呢?因為str6、str7是動態字串,即非字面值的字串。
《CLR Via C#》