Visual Basic 2005 - 善用 StringBuilder 提升字串處理效率

摘要:Visual Basic 2005 - 善用 StringBuilder 提升字串處理效率

我們必須開門見山地說,在相同字串的許多操作上,使用 StringBuilder 類別會比使用一個 String 物件來得更有效率。 

大家務必瞭解,System.String 資料型別(或是說 String 物 件)代表的是一種不變的字串,也就是說,一旦您設定其值,您就不能更改它。如果您嚐試要插入、刪除或更改字串的任何部分,唯一的方式就是去建立一個新的字 串。說得更專業點,每次字串資料變更時,記憶體中該字串原來的表示法就會被破壞掉,並建立內含新字串資料的新表示法,此舉會引發對記憶體的配置作業以及對 記憶體的反配置作業。當然,這些作業都是在幕後完成的,因此真正的成本並不會立刻顯現。配置與反配置記憶體會加重 Common Language RuntimeCLR) 裡記憶體管理和記憶體回收的相關工作,所以絕對是要付出代價的,直接的影響,就是增了處理時間。這種情況在迅速地接連配置和反配置佔有大塊記憶體的字串時 尤其明顯,就像在大量字串串連時所發生的情況一樣。雖然這種情形在單一使用者環境裡不會帶來任何問題,但是在伺服器的環境裡使用時(比方說,在 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類別。

' StringBuilder
是由System.Text命名空
'
間所提供,我們已在程式的開頭處匯
'
入此命名空間(Imports System.Text)。
Dim sb As New StringBuilder( _
  "
台灣微軟技術社群暨" & _
  "
最有價值專家 MVP")

sb.Insert(19, _
  "
Most Valuable Professional")
sb.Remove(9, 6)
sb.Replace("
技術社群", " Community ")
sb.AppendFormat( _
  "{0}
年內{1}度當選。", "", "")

'
假設您要在單字 "MVP" 之後插入
'
一個句點,您必須先找到此單字,然
'
後再於該單字之後插入一個句點。欲
'
找到此單字,您必須使用IndexOf
'
加以搜尋,但是StringBuilder並未提
'
供此方法。因此,您務必使用ToString
'
方法來取得字串,然後處理該字串。
Dim intPos As Integer
intPos = sb.ToString.IndexOf("MVP")
If intPos > 0 Then
   '
插入逗點的位置就是您所搜尋到
   '
的位置再加上單字 "MVP" 的長
   '
度。
   sb.Insert( _
     intPos + "MVP".Length, ", ")
End If

txtResults.AppendText( _
 Environment.NewLine & _
 "StringBuilder
輸出:" & _
 Environment.NewLine & _
 sb.ToString & Environment.NewLine)

' 同樣的程式碼如果直接使用String
'
件來處理,將如下所示。請注意到
' .NET Framework
需要去建立新字串
'
的次數。StringBuilder 顯然會比此種
'
操作來得更有效率。
Dim str As String = _
  "
台灣微軟技術社群暨" & _
  "
最有價值專家 MVP"

str = str.Insert(19, _
  "
Most Valuable Professional")
str = str.Remove(9, 6)
str = str.Replace( _
  "
技術社群", " Community ")
str &= String.Format( _
  "{0}
年內{1}度當選。", "", "")
Dim intPos As Integer
intPos = str.IndexOf("MVP")
If intPos > 0 Then
   str = str.Insert( _
     intPos + "MVP".Length, ", ")
End If

txtResults.AppendText( _
  Environment.NewLine & _
  "String
輸出:" & _
  Environment.NewLine & _
  str & Environment.NewLine)

圖表 3String類別與StringBuilder類別使用比較 

經由以上的說明,相信大家都對 StringBuilder 都已經抱持一個非常肯定的態度,多多利用它準沒錯。 

本文節錄自Visual Basic 2005程式開發與介面設計秘訣」一書 

章立民研究室 2007/1/2