String和StringBuilder的深入解析
前言:本文出發點是我們開發的過程中是否真正的理解stringbuilder的使用,string字符串操作的是如何實現(哈希表),stringbuilder是否設置默認值容量,什么時候才用stringbuilder。
一概念String和stringbulider的理解
string是我們用的最多的類型之一,是一個特殊的引用類型,直接派生於Object,因此它的值儲存在托管堆上。構造一個新字符串的時候,不需要用new。它是”不可變的“。初始化字符串對象后,該字符串對象的長度、內容都是確定不變的了。可以思考這個時候,我們需要更改或者添加字符串,會做一個怎樣的動作呢?
StringBulider因為string的”不可變“,導致多次修改字符串的時候會損耗性能,.net為了解決這個問題,提供了動態創建string的方法,以克服string不可變帶來的性能損耗。StringBuilder和String比起來,功能較少,只有基本的屬性和增刪改的方法。但是你知道stringbuilder也有一個固定的容量值嗎??,注意:StringBulider容量 (默認是16)雖然 StringBuilder 對象是動態對象,允許擴充它所封裝的字符串中字符的數量,但是您可以為它可容納的最大字符數指定一個值。此值稱為該對象的容量,不應將它與當前 StringBuilder 對象容納的字符串長度混淆在一起。例如,可以創建 StringBuilder 類的帶有字符串“Hello”(長度為 5)的一個新實例,同時可以指定該對象的最大容量為 25。當修改 StringBuilder 時,在達到容量之前,它不會為其自己重新分配空間。當達到容量時,將自動分配新的空間且容量翻倍。可以使用重載的構造函數之一來指定StringBuilder類的容量。以下代碼示例指定可以將 MyStringBuilder對象擴充到最大25個空白。 StringBuilder MyStringBuilder = new StringBuilder("Hello World!", 25); 另外,可以使用讀/寫 Capacity 屬性來設置對象的最大長度。以下代碼示例使用 Capacity 屬性來定義對象的最大長度。 MyStringBuilder.Capacity = 25; EnsureCapacity 方法可用來檢查當前 StringBuilder 的容量。如果容量大於傳遞的值,則不進行任何更改;但是,如果容量小於傳遞的值,則會更改當前的容量以使其與傳遞的值匹配。 也可以查看或設置 Length 屬性。如果將 Length 屬性設置為大於 Capacity 屬性的值,則自動將 Capacity 屬性更改為與 Length 屬性相同的值。如果將 Length 屬性設置為小於當前 StringBuilder 對象內的字符串長度的值,則會縮短該字符串。
二、為什么說變動影響性能。(string和StringBuilder)
String:string s = "I am ";s += "Sky";怎么分配內存的呢?
備注:如果每次都這樣重新分配真實瘋了,.net肯定沒有那么傻了,最起碼要避免下如果兩個string的字符串一模一樣,我是不是不需要分配新的堆,只需要制定同樣的引用就好了呢?下面就出現了一個名詞:字符串留用,CLR初始化的時候會創建哈希表,每構建一個新字符串都會與哈希表匹配,查找是否有相同的字符串,如果匹配,就會返回這個已存在的舊對象,由新變量進行引用。否則,就會創建一個字符串副本添加到哈希表里,Key就是字符串,Value就是string對象在堆上的地址。
是不是所有的都是這樣呢,有什么特殊情況嗎?
總結New出來的對象是不會記錄在哈希表。
tringBuilder 對象維護一個緩沖區,以便容納新數據的串聯。如果有足夠的空間,新數據將被追加到緩沖區的末尾;否則,將分配一個新的、更大的緩沖區,原始緩沖區中的數據被復制到新的緩沖區,然后將新數據追加到新的緩沖區。
內部實現原理
總結:StringBuffer是可變類,和線程安全的字符串操作類,任何對它指向的字符串的操作都不會產生新的對象。 每個StringBuffer對象都有一定的緩沖區容量,當字符串大小沒有超過容量時,不會分配新的容量,當字符串大小超過容量時,會自動增加容量。事實是,StringBuilder比string快的原因是string拼接時產生了中間對象,最終是垃圾。如: string str = "a";str += "b";str += "c";那么,最終結果是"abc",但第二行產生了"ab"只是一個中間對象,是個垃圾。用StringBuilder會避免這種中間對象的產生。那如果我這么寫呢? string str ="a"+"b"+ "c";這會比StringBuilder慢嗎?不會。
中間對象的產生才是影響性能的主要原因。
三、測試案例:
private void button1_Click(object sender, EventArgs e)
{
int number =int.Parse( textBox1.Text.ToString());
GetStringTime(number);
GetStringBulider(number);
GetStringTime1(number);
GetStringBulider1(number);
}
/// <summary>
/// 測試string 性能時間
/// </summary>
private void GetStringTime(int number)
{
Stopwatch watch = new Stopwatch();
List< String> li = new List< string>();
watch.Start();
string str = "select * from testa inner join 快速排序耗費時間 快速排序耗費時間 快速排序耗費時間";
for (int i = 0; i < number; i++)
{
li.Add(str);
}
watch.Stop();
label1.Text = watch.ElapsedMilliseconds.ToString();
}
private void GetStringBulider(int number)
{
Stopwatch watch = new Stopwatch();
List<String> li = new List<string>();
watch.Start();
StringBuilder strb = new StringBuilder();
strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗費時間快速排序耗費時間");
for (int i = 0; i < number; i++)
{
li.Add(strb.ToString());
}
watch.Stop();
label2.Text = watch.ElapsedMilliseconds.ToString();
}
/// <summary>
/// 測試string 性能時間變化
/// </summary>
private void GetStringTime1(int number)
{
Stopwatch watch = new Stopwatch();
List<String> li = new List<string>();
watch.Start();
string str = "select * from testa inner join 快速排序耗費時間 快速排序耗費時間 快速排序耗費時間";
for (int i = 0; i < number; i++)
{
str = str+"select * from testa inner join 快速排序耗費時間 快速排序耗費時間 快速排序耗費時間";
}
watch.Stop();
label1.Text =label1.Text+"不變,變化"+ watch.ElapsedMilliseconds.ToString();
}
/// <summary>
/// 測試stringBulider 變化的性能
/// </summary>
/// <param name="number"></param>
private void GetStringBulider1(int number)
{
Stopwatch watch = new Stopwatch();
List<String> li = new List<string>();
watch.Start();
StringBuilder strb = new StringBuilder();
strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗費時間快速排序耗費時間");
for (int i = 0; i < number; i++)
{
strb.Append("select * from testa inner join dsadfasfsadfa快速排序耗費時間快速排序耗費時間");
}
watch.Stop();
label2.Text =label2.Text+"不變,變化"+ watch.ElapsedMilliseconds.ToString();
}
效果圖如下:
備注:每圖第一行表示string,第二行表示stringbulider,變化表示str++的意思或append。
四:總結:String 和stringbulider的整體匯總。
1. Sting是恆定的,string部里的人是可變化的。
2. 對於簡單的字符串連接操作,在性能上stringbuilder不一定總是優於string。因為stringbulider對象的創建也消耗大量的性能,在字符串連接比較少的情況下,過度濫用stringbuilder會導致性能的浪費而非節約,只有大量無法預知次數的字符串操作才考慮stringbuilder的使用。從最后分析可以看出如果是100行以內根本看不出太大差別。
3. Stringbulider的使用,最好制定合適的容量值,否則優於默認值容量不足而頻繁的進行內存分配操作,是不妥的實現方法。
可以深思,第一我們對適合的容量值處理了嗎? 第二,我們是不是一再提要使用stringbuilder說性能好,但是在100行內的字符操作有分別嗎。
簡單的字符串連接操作可以適度思考下 string.Concat 和 string.join 的使用。(string.concat的裝箱操作)。
參考文章:
http://www.cnblogs.com/juqiang/archive/2005/04/19/140538.html。
http://www.cnblogs.com/huangxincheng/p/4042105.html。
http://www.cnblogs.com/heartstill/archive/2011/11/11/2245411.html。
http://www.cnblogs.com/kid-li/archive/2006/10/18/532174.html。
http://www.cnblogs.com/gfwei/archive/2007/03/14/674499.html。
http://www.cnblogs.com/skychen1218/p/3593678.html。
書籍:《你必須知道的.net》什么是string(345頁)。