System.String 類型一直是我們不斷討論的話題,它是一個用於對字符串進行存儲和操作的這么一個類型。
System.String 也是 C# 基礎類型中唯一的引用類型。但是,它卻具有很多值類型的特點。
我們來看一段簡單的代碼:
1 string text = "White";
2 string temp = text;
3 temp = "Black";
4 Console.WriteLine(text);
5 Console.ReadKey();
按照引用的理論,此處 temp 變量應該是存儲的 text 變量的地址,那么修改 temp 變量的值,text 的值就應該隨之改變。
那么,此時 text 變量的值應該就是 "Black",但事實上經過測試 text 變量的值還是 "White"。
那就說明 temp 變量肯定不是存儲的 text 變量的地址。但,這樣又違背了它是引用類型的這一特點,那它的內部究竟是怎么樣處理的呢?
據我了解,微軟應該是在 String 類型中引入了 Copy-On-Write(寫時拷貝) 技術,先來簡要說明一下什么是 Copy-On-Write 技術:
簡單來說,在復制一個對象時並不是真的在內存中把原來對象的數據復制一份到另外一個地址,而是在新對象的內存映射表中指向同原對象相同的位置,
並且把那塊內存的 Copy-On-Write 位設為 1。在對這個對象執行讀操作的時候,內存數據沒有變動,直接執行就可以。
在寫的時候,才真正將原始對象復制一份到新的地址,修改新對象的內存映射表到這個新的位置,然后往這里寫。
有一定經驗的程序員應該都知道,Copy-On-Write(寫時拷貝) 技術使用了 "引用計數" 方式,會有一個變量用於保存引用的數量。
當第一個類構造時,String 的構造函數會根據傳入的參數從堆上分配內存,當有其它類需要這塊內存時,這個計數為自動累加。
當有類析構時,這個計數會減一,直到最后一個類析構時,此時的引用計數為 1 或是 0,此時,程序才會真正的釋放這塊從堆上分配的內存。
說白了,"引用計數" 就是 String 類中寫時拷貝的原理!
事實上,String 還是一個不可變的數據類型,一旦對 String 類型的對象進行了初始化,該字符串對象就不能改變了。
為了說明這一點,我們再來看一小段很簡單的代碼:
1 string name = "Adamas";
2 name += " is a gentleman.";
在執行這段代碼時,首先創建一個名為 name 的 String 類型的對象,並初始化為 "Adamas"。
此時,.NET 運行庫會為該字符串分配足夠的內存來保存這個文本,然后,再設置變量 name,來表示這個字符串實例。
從語法上看,第二行代碼是將更多的文本添加到此字符串中。在我初學 C# 語言的時候,我也是這么理解的。
實際上卻並非如此,而是創建一個新的字符串實例,給他分配足夠的內存,然后存儲合並的所有文本。
把第一行代碼中的文本:"Adamas" 復制到新字符串中,再加上第二行代碼中的文本:" is a gentleman."。
然后更新存儲在 name 變量中的內存地址,使變量正確的指向新的字符串對象。舊的字符串對象被銷毀了引用,並等待系統回收。
這樣的方式本身並沒有問題,但是如果需要頻繁的進行字符串的操作的話,那就存在極大地性能問題。
因此,為了解決這一問題,微軟推出了 System.Text.StringBuilder 類。在 StringBuilder 類中,僅限於替換、添加和刪除字符串中文本的操作,但它的效率遠遠高於 String。
StringBuilder stringBuilder = new StringBuilder(30,300);
StringBuilder 類在初始化的時候,提供許多構造函數用來初始化當前實例的初始大小和可存儲的最大字符數以及用來初始化當前實例的字符串。
實際上,當我們創建 StringBuilder 對象的時候,.NET 運行庫會為當前的對象在內存中分配一塊緩存區域,用以對字符串操作的預留空間。
在使用 StringBuilder 類的時候,最好將容量設置為字符串可能的最大長度,確保 StringBuilder 不需要重復分配內存。
如果字符的容量超過設置的最大容量,.NET 運行庫將自動分配內存並翻倍。
對於我們 .NET 程序員而言,StringBuilder 與 String 的不同之處就在於,StringBuilder 可以顯示的設置分配內存的大小,
而 String 只能根據你初始化時的字符串的大小由系統分配足夠的內存。
所以,當要對字符串進行頻繁的操作的時候,在 String 和 StringBuilder 之間,我們應該選擇 StringBuilder。
