1. 程式人生 > 實用技巧 >.net 中的 StringBuilder 和 TextWriter 區別

.net 中的 StringBuilder 和 TextWriter 區別

最近閒來之餘,看了一些開源的類庫,看到有些類庫喜歡用TextWriter類來記錄相關的字串資料,感到比較好奇,為啥不用StringBuilder類物件。於是在網上搜索了一番,總結了相關筆記。

StringBuilder

在 .net 中,字串作為一種基本的資料型別,通常在一個程式中同一個字串只維護一個副本。也就是說,通過直接給定字串值的字串引用會引用到相同資料上。這種處理的好處在於它能夠減少字串所佔用的記憶體空間,不需要為多個同樣的字串開闢多次空間。在C#中 string 型別是一個不變數,給字串引用賦予新值並不會改變對應記憶體中的資料,而是設定引用為新字串位置。

在平時,這種處理邏輯能夠大大減少字串所佔用的記憶體空間,但有的時候,也會起一些反效果,典型的例子就是在一些構造字串的操作時所生成的中間字串資料。舉個例子:

string[] words = {"Nice ", "to ", "meet ", "you."};
string sentence = "";
for(i = 0;i < words.Length; i++)
{
    sentence += words[i];
}

這是一個很簡單的字串組裝功能,它將給定的單詞拼接成一個句子,我們希望的是直接拼接成最後的結果,但這段程式碼除了生成最終句子外,先前的臨時也會生成出來。也就是說,"Nice "、"Nice to "、"Nice to meet "以及最後字串"Nice to meet you."會隨著一次次迴圈迭代全部構造出來。但實際上,對於我們來說只需要最後句子即可,中間部分完全不需要。為此,我們需要新的方式來避免無意義的開銷。

StringBuilder類就是一種動態靈活地構造字串的方法。這種構造字串的好處在於,它能夠避免構造中間字串結果,轉而直接生成最終的字串資料。按照上面的例子,稍作修改就能得到一個性能更加優異的版本,在該版本下只有最後的句子字串才會被生成。

string[] words = {"Nice ", "to ", "meet ", "you."};
StringBuilder sentenceBuilder = new StringBuilder();
for(i = 0;i < words.Length; i++)
{
    sentenceBuilder.Append(words[i]);
}
string setence = sentenceBuilder.ToString();

至於StringBuilder類的原理,我個人猜測是該類中維護一個char型列表,然後動態地修改陣列元素,達到每次拼接時不會生成字串的目的,只有當顯式呼叫命令生成時,才會生成。不過,因為能力有限,我還不知道怎麼在runtime這個開源庫中找StringBuilder的實現。

TextWriter

TextWriter是一個抽象類,按照微軟官方給出的描述,該類指的是可以編寫一個有序字元系列的編寫器。嗯,字都認識,但是這句話感覺就不像是人說的。實際上,我個人對這個類的理解是它是一個寫入器。換句話來說,TextWriter描述了一個寫入的過程,但具體寫什麼?向哪裡寫入?這不是這個抽象類所關心的話題,而是由其子類所負責。

.net中常用內建的一些子類:

  • StreamWriter :這個類相信很多人都熟悉,當需要向檔案中寫入資料時,往往通過該類寫入資料
  • StringWriter :今天本文所需要研究的物件,向字串寫入
  • HttpWriter : 向網路流中寫入資料

StringWriter類作為TextWriter的一個繼承類,按照MSDN給出的解釋是,用於將資訊寫入字串的TextWriter類物件,這個類看起來和StringBuilder類所做的功能差不多,那麼為什麼在 .net 中設計兩個不同類做同一個功能呢?翻了下相關資料,只能說這兩個類是不同設計思路下的產物。StringBuilder是一種靈活構建字串的類,它不會產生額外的臨時字串,而StringWriter則將字串資料作為一種寫入的目的地,從這個角度來看,確實也是一種必要的實現。

比如說,有一個函式,它專門是將字串資料記錄下來,具體點,可以想像為日誌記錄器將日誌資訊記錄到某個地方。這樣的情況下,我們提供兩個輸入引數,TextWriter類物件表明是一個寫入器,message描述一個日誌資訊,那麼記錄資料只需要這樣寫就可以了:

public void WriteData(TextWriter writer, string message)
{
    writer.Write(message);
}

這樣一來,如果將資訊記錄到某個檔案中,只要這樣寫:

using var file = new FileStream("./log.txt", FileAccess.Write);
using var writer = new StreamWriter(file);
WriteData(writer, "hello");

如果想將資訊記錄到某個變數中,就是這樣:

var writer = new StringBuilder();
WriteData(writer, "hello");
data = writer.ToString();

總結

總的來說,如果只是單純使用字串而不涉及到修改字串值時,直接使用string型別即可。如果需求是更加專注構造某種字串資料,那麼使用StringBuilder是一個比較好的選擇。如果需求強調的是將某種格式的字串資料寫入到某個介質中,使用TextWriter對應的繼承類會更好,更符合封裝的思想,且不需要過多關注資料是怎麼寫入的,只要將需要寫入的資料傳入到其中即可。