[讀書筆記]C#學習筆記八:StringBuilder與String詳解及參數傳遞問題剖析


前言

上次在公司開會時有同事分享windebug的知識, 拿的是string字符串Concat拼接 然后用while(true){}死循環的Demo來講解.
其中有提及string操作大量字符串效率低下的問題, 剛好自己之前也看過類似的問題, 於是便拿出來記錄一下.
本文內容: 參數傳遞問題剖析, string與stringbuilder詳解

1,參數傳遞問題剖析

對於C#中的參數傳遞,根據參數的類型可以分為四類:

  • 值類型參數的按值傳遞
  • 引用類型參數的按值傳遞
  • 值類型參數的按引用傳遞
  • 引用類型參數的按引用傳遞

1.1值類型參數的按值傳遞

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         
 6         int addNum = 1;
 7         // addNum 就是實參,
 8         Add(addNum); 
 9      }
10 
11     // addnum就是形參,也就是被調用方法中的參數
12     private static void Add(int addnum)
13     {
14         addnum = addnum + 1;
15         Console.WriteLine(addnum);
16     }
17 }

對於值類型的按值傳遞,傳遞的是該值類型實例的一個拷貝,也就是形參此時接受到的是實參的一個副本,被調用方法操作是實參的一個拷貝,所以此時並不影響原來調用方法中的參數值,為了證明這點,看看下面的代碼和運行結果就明白了:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         // 1. 值類型按值傳遞情況
 6         Console.WriteLine("按值傳遞的情況");
 7         int addNum = 1;
 8         Add(addNum);
 9         Console.WriteLine(addNum);   
10       
11         Console.Read();
12     }
13 
14     // 1. 值類型按值傳遞情況
15     private static void Add(int addnum)
16     {
17         addnum = addnum + 1;
18         Console.WriteLine(addnum);
19     }
20 }

 

運行結果是: 
按值傳遞的情況
2
1
從結果中可以看出addNum調用方法之后它的值並沒有改變,Add 方法的調用只是改變了addNum的副本addnum的值,所以addnum的值修改為2了。具體的分析請看下面的圖:


1.2引用類型參數的按值傳遞
當傳遞的參數是引用類型的時候,傳遞和操作的是指向對象的引用(看到這里,有些朋友會覺得此時不是傳遞引用嗎?怎么還是按值傳遞了?對於這個疑惑,此時確實是按值傳遞,此時傳遞的對象的地址,傳遞地址本身也是傳遞這個地址的值,所以此時仍然是按值傳遞的),此時方法的操作就會改變原來的對象。代碼如下:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         // 2. 引用類型按值傳遞情況
 6         RefClass refClass = new RefClass();
 7         AddRef(refClass);
 8         Console.WriteLine(refClass.addnum);
 9     }    
10      // 2. 引用類型按值傳遞情況
11     private static void AddRef(RefClass addnumRef)
12     {
13         addnumRef.addnum += 1;
14         Console.WriteLine(addnumRef.addnum);
15     }
16 }
17 class RefClass
18 {
19     public int addnum=1;
20 }

運行結果為:
2
2
為什么此時傳遞引用就會修改原來實參中的值呢?對於這點我們還是參數在內存中分布圖來解釋下:

1.3string引用類型參數的按值傳遞的特殊情況
對於String類型同樣是引用類型,然而對於string類型的按值傳遞時,此時引用類型的按值傳遞卻不會修改實參的值,可能很多朋友對於這點很困惑,下面具體看看下面的代碼:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5           // 3. String引用類型的按值傳遞的特殊情況
 6         string str = "old string";
 7         ChangeStr(str);
 8         Console.WriteLine(str);
 9         
10     }
11     
12      // 3. String引用類型的按值傳遞的特殊情況
13     private static void ChangeStr(string oldStr)
14     {
15         oldStr = "New string";
16         Console.WriteLine(oldStr);
17     }
18 }

運行結果為:

New string
old string
對於為什么原來的值沒有被改變主要是因為string的“不變性”,所以在被調用方法中執行 oldStr="New string"代碼時,此時並不會直接修改oldStr中的"old string"值為"New string",因為string類型是不變的,不可修改的,此時內存會重新分配一塊內存,然后把這塊內存中的值修改為 “New string”,然后把內存中地址賦值給oldStr變量,所以此時str仍然指向 "old string"字符,而oldStr卻改變了指向,它最后指向了 "New string"字符串。所以運行結果才會像上面這樣,下面內存分布圖可以幫助你更形象地理解文字表述:

1.4按引用傳遞

不管是值類型還是引用類型,我們都可以使用ref 或out關鍵字來實現參數的按引用傳遞,然而按引用進行傳遞的時候,需要注意下面兩點:

方法的定義和方法調用都必須同時顯式使用ref或out,否則會出現編譯錯誤

CLR允許通過out 或ref參數來實現方法重載。如:

 1 #region CLR 允許out或ref參數來實現方法重載
 2 private static void Add(string str)
 3 {
 4     Console.WriteLine(str);
 5 }
 6 
 7 // 編譯器會認為下面的方法是另一個方法,從而實現方法重載
 8 private static void Add(ref string str)
 9 {
10     Console.WriteLine(str);
11 }
12 #endregion

按引用傳遞可以解決由於值傳遞時改變引用副本而不影響引用本身的問題,此時傳遞的是引用的引用(也就是地址的地址),而不是引用的拷貝(副本)。下面就具體看看按引用傳遞的代碼:

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     { 
 5         #region 按引用傳遞
 6         Console.WriteLine("按引用傳遞的情況");
 7         int num = 1;
 8         string refStr = "Old string";
 9         ChangeByValue(ref num);
10         Console.WriteLine(num);
11         changeByRef(ref refStr);
12         Console.WriteLine(refStr);
13         #endregion 
14 
15         Console.Read();
16     }
17 
18     #region 按引用傳遞
19     // 1. 值類型的按引用傳遞情況
20     private static void ChangeByValue(ref int numValue)
21     {
22         numValue = 10;
23         Console.WriteLine(numValue);
24     }
25 
26     // 2. 引用類型的按引用傳遞情況
27     private static void changeByRef(ref string numRef)
28     {
29         numRef = "new string";
30         Console.WriteLine(numRef);
31     }
32 
33     #endregion
34 }

運行結果為:
按引用傳遞的情況
10
10
new string
new string
從運行結果可以看出,此時引用本身的值也被改變了,通過下面一張圖來幫忙大家理解下按引用傳遞的方式:



到這里參數的傳遞所有內容就介紹完了。總之,對於按值傳遞,不管是值類型還是引用類型的按值傳遞,都是傳遞實參的一個拷貝,只是值類型時,此時傳遞的是實參實例的一個拷貝(也就是值類型值的一個拷貝),而引用類型時,此時傳遞的實參引用的副本。對於按引用傳遞,傳遞的都是參數地址,也就是實例的指針。

2, string與stringBuilder的內部實現
大家應該知道如果做大量的字符串拼接的話, string的效率明顯是低於stringBuilder的, 至於示例我這里就不在列出了,下面給出個鏈接可以查看下.
我這里只是從string和stringBuilder源碼說起, 通過源代碼的實現方式來說明stringBuilder為何比string效率高.

StringBuilder vs String+String(String concatenation):
通常情況下,4~8個字符串之間的連接,String+String的效率更高。
答案來自: http://stackoverflow.com/a/1612819
StringBuilder vs String.concat():
如果在編譯期間不能確定要連接的字符串個數,用StringBuilder更合適。
答案來自: http://stackoverflow.com/a/4191142

下面先給出結論:

stringbuilder內部維護一個字符數組。下次追加的字符串,直接占用空余的位置。 
如果超出上限。數組增大為原來的兩倍(兩倍還不夠就直接增大到足夠寬度),然后覆蓋原來的數組.

String是不可改變的。每次使用System.String類中的方法之一時,都要在內存中創建一個新的字符串對象,這就需要為該新對象分配新的空間。
在需要對字符串執行重復修改的情況下,與創建新的String對象相關的系統開銷可能會非常昂貴。

那么下面就看看string和stringBuilder源碼有和區別吧, 我這里是使用的Reflector查看的:
(1)string

打開Reflector,找到string類

找到Concat方法, 我們這里以Concat為例:

下面我們在看下FillStringChecked(dest, 0, str0)的實現方式:


所以看到這里結論就出來了: 當我們隊字符串進行大量操作的時候, 會產生很多的新的字符串, 這些字符串會大量零碎的占據着堆空間, 大多都是生存期較短的, 會對gc產生比較大的回收壓力.


(2)stringBuilder

看這個類的話,還是看一下它的源代碼,以Append吧,從下面這個截圖中看出來幾個有意思的地方。

<1> 原來StringBuilder里面維護的是一個m_ChunkChars的字符數組。

<2> 如果當前的字符串的length<2,會直接給chunkchars數組復制,length>2的時候看到的是剛才string類中經典的wstrcpy用法,而

      這個時候ptr指向的是chunkChars[chunkLength]的首地址,而不像string中申請新的內存空間,所以從這里看,比string大大的節省

    了內存空間。




更多細節內容請看@老趙點滴 大神的博客內容吧:

重談字符串連接性能上:http://blog.zhaojie.me/2009/11/string-concat-perf-1-benchmark.html
重談字符串連接性能中:http://blog.zhaojie.me/2009/12/string-concat-perf-2-stringbuilder-implementations.html
重談字符串連接性能下:http://blog.zhaojie.me/2009/12/string-concat-perf-3-profiling-analysis.html

PS:好了, 到了這里@Learning Hard的<<C#讀書筆記>> 的讀書筆記就分享完了. 后面開始自己學Asp.Net(以前學的是java, 接觸最多的是jsp, 到了公司開始做.Net), 對於Asp.Net還不是太了解, 希望用一段時間可以掌握這個. 另外空閑時間還在讀<<CLR via C#>>這本書, 很不錯的一本書, 需要慢慢去消化.

口語小貼士:
Be my guest.
請便,別客氣.
Boy will be boys.
本性難移!
Don't beat around the bush.
別拐彎抹角了.
Don't bury your head in the sand.
不要逃避現實了.
Don't get me wrong.
不要誤會我
Don't get on my nerves!
不要攪的我心煩
Don't look wise.
別自作聰明.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM