摘要:Visual Basic 2005 - 善用 StringBuilder 提升字串處理效率
我們必須開門見山地說,在相同字串的許多操作上,使用 StringBuilder 類別會比使用一個 String 物件來得更有效率。
大家務必瞭解,System.String 資料型別(或是說 String 物 件)代表的是一種不變的字串,也就是說,一旦您設定其值,您就不能更改它。如果您嚐試要插入、刪除或更改字串的任何部分,唯一的方式就是去建立一個新的字 串。說得更專業點,每次字串資料變更時,記憶體中該字串原來的表示法就會被破壞掉,並建立內含新字串資料的新表示法,此舉會引發對記憶體的配置作業以及對 記憶體的反配置作業。當然,這些作業都是在幕後完成的,因此真正的成本並不會立刻顯現。配置與反配置記憶體會加重 Common Language Runtime(CLR) 裡記憶體管理和記憶體回收的相關工作,所以絕對是要付出代價的,直接的影響,就是增了處理時間。這種情況在迅速地接連配置和反配置佔有大塊記憶體的字串時 尤其明顯,就像在大量字串串連時所發生的情況一樣。雖然這種情形在單一使用者環境裡不會帶來任何問題,但是在伺服器的環境裡使用時(比方說,在 Web 伺服器上執行的 ASP.NET® 應用程式中),卻可能在效能與延展性上造成極為嚴重的問題。
以下面這一段程式碼而言(請參閱 CH3_DemoForm017.vb 之「使用 String」按鈕的 Click 事件處理常式),您知道在每一次的迴圈中,會發生多少次的字串配置作業嗎?答案是 14。在此種寫法中,「&」(或「+」)運算子會使得變數 sXml 所指向的字串被破壞掉然後再重新建立。再次提醒您,字串配置是很花時間的,而且隨著字串的增長,情況會越來越嚴重。這正是 .NET Framework 為什麼要提供 StringBuilder 類別的原因:
Dim nRep As Int32
Dim Reps As Int32 = Convert.ToInt32(numLoops.Value)
Dim Start, Finish As Double
Start = Microsoft.VisualBasic.DateAndTime.Timer
Dim sXml As String = ""
For nRep = 1 To Reps
sXml &= "<訂單 訂單號碼=""" _
& nRep _
& """ 訂貨日期=""" _
& DateTime.Now.ToString() _
& """ 客戶編號=""" _
& nRep _
& """ 產品編號=""" _
& nRep _
& """ 產品說明=""" _
& "此產品的 Id 是: " _
& nRep _
& """ 數量=""" _
& nRep _
& """/>"
Next nRep
sXml = "<訂單 方法=""1"">" & sXml & "訂單>"
Finish = Microsoft.VisualBasic.DateAndTime.Timer
txtStringResult.Text = (Finish – Start).ToString
與 System.String 類別相較之下,System.Text.StringBuilder 類別則會保留它自己的字串緩衝區。在針對 StringBuilder 執行作業因而可能會改變字串資料的長度時,StringBuilder 會先檢查緩衝區的大小是否足夠容納新的字串資料。如果不夠,則緩衝區的大小就會增加預先決定的數量。由於大幅降低記憶體配置操作的發生機率,因此自然能有效提升效能。
就以字串串連作業而言,標準的串連方式會導致為每一項串連作業建立一個新字串,但是 StringBuilder 卻每次都使用相同的字串緩衝區。基本上,當您只要修改字串,但是不要建立新的字串物件時,就應該使用 System.Text 命名空間中的 StringBuilder 類別。舉例來說,當需要在迴圈中串連多個字串時,使用 StringBuilder 類別將可具體提升效能。此外,StringBuilder 類別還提供了非常有效率的 Replace 方法,它可以用來取代 String.Replace。
基本上,StringBuilder 方法在執行上會比標準串連方式優異的原因取決於幾項因素,其中包括串連的數目、所要建置之字串的大小、以及 StringBuilder 的緩衝區初始化參數是否設定得夠好。請注意,在大多數的情況下,多估算一些緩衝區空間要比後來又不斷加大來得更好。
以先前在迴圈中進行字串串連作業的程式碼來說,如果使用 StringBuilder 將其改寫如下(請參閱CH3_DemoForm017.vb之「使用 StringBuilder」按鈕的 Click 事件處理常式),將能夠大幅提升執行速度,而且如果迴圈的數目愈多,提升的幅度愈明顯:
Dim nRep As Int32
Dim Reps As Int32 = Convert.ToInt32(numLoops.Value)
Dim Start, Finish As Double
Start = Microsoft.VisualBasic.DateAndTime.Timer
Dim oSB As StringBuilder
' 確保StringBuilder的容量足以容納最終的文字
oSB = New StringBuilder(Reps * 165)
oSB.Append("<訂單 方法=""2"">")
For nRep = 1 To Reps
oSB.Append("<訂單 訂單號碼=""")
oSB.Append(nRep)
oSB.Append(""" 訂貨日期=""")
oSB.Append(DateTime.Now.ToString())
oSB.Append(""" 客戶編號=""")
oSB.Append(nRep)
oSB.Append(""" 產品編號=""")
oSB.Append(nRep)
oSB.Append(""" 產品說明=""")
oSB.Append("此產品的 Id 是: ")
oSB.Append(nRep)
oSB.Append(""" 數量=""")
oSB.Append(nRep)
oSB.Append("""/>")
Next nRep
oSB.Append("訂單>")
Finish = Microsoft.VisualBasic.DateAndTime.Timer
txtStringBuilderResult.Text = (Finish – Start).ToString
圖表 1
圖表1所示者是程式範例CH3_DemoForm017.vb的執行畫面,其主要目的在於讓您比較先前所列之兩種字串串連作業寫法(String vs StringBuilder)在執行速度上的差異。您可以先設定迴圈數,然後依序按一下「使用String」按鈕與「使用StringBuilder」按鈕,從「處理時間」文字方塊中所列的數據可以發現,使用StringBuilder類別進行字串串連作業的執行速度要比使用String類別快上好幾十倍,可說是天壤之別。
請注意:
欲使用StringBuilder類別,請記得匯入命名空間System.Text。
不過我們也必須坦白地說,StringBuilder類別雖然好用,然而它也也非萬能。事實上,並非所有的String類別方法都可以直接使用StringBuilder來完成,當您恰巧需要String類別所獨有的方法時(例如:IndexOf),您必須將內容提取成一個文字串,針對字串執行您所需的操作,然後再繼續使用StringBuilder。
圖表2所示者是程式範例CH3_DemoForm018.vb的執行畫面,它示範如何分別使用StringBuilder類別與String類別來完成同一項作業。圖表3同時列出這兩種寫法的程式碼,以方便您比較之間的差異。本範例特別值得一提的地方是,由於我們要在單字「MVP」之後插入一個句點,因此必須先找到此單字,然後再於該單字之後插入一個句點。欲找到此單字,您必須使用IndexOf方法來加以搜尋,但是StringBuilder並未提供此方法,所以我們只好使用ToString方法來取得字串,然後處理該字串。
圖表 2
使用StringBuilder類別 | 使用String類別 |
' 示範如何使用StringBuilder類別。 | ' 同樣的程式碼如果直接使用String物 |
圖表 3、String類別與StringBuilder類別使用比較
經由以上的說明,相信大家都對 StringBuilder 都已經抱持一個非常肯定的態度,多多利用它準沒錯。
本文節錄自「Visual Basic 2005程式開發與介面設計秘訣」一書。
章立民研究室