1. 前言
提醒一下,C# 裡的字串是 不可變的(immutable)。意思就是,字串一旦建立就不能改變:任何看起來像是「改變」字串的操作,其實都會在記憶體裡創建一個新的字串。
舉個例子,當你寫:
string hello = "哈囉";
hello += " 世界!"; // 看起來 hello 變了,其實是創建了新物件!
變數 hello 會指向一個新物件,舊的(如果沒被用到)之後會被垃圾回收器清掉。如果你在迴圈裡這樣做很多次,記憶體可能會「塞爆」,效能也會突然變很差。這在你要合併成千上萬個字串時特別明顯:每一步都會創建新字串、複製所有字元,舊物件會一直等到垃圾回收,然後你的程式就開始卡卡的。
這時候我們這堂課的主角就要登場了 —— StringBuilder。
using System.Text; // ← 一定要加!
StringBuilder sb = new StringBuilder("哈囉");
sb.Append(" 世界!"); // 現在字串變得又快又有效率
認識 StringBuilder
StringBuilder 是 System.Text 命名空間裡的 class。它的任務就是高效地建立和組合大量字串,可以直接改變內容,不用一直創建新物件。
比喻一下:
如果字串(string)像是一塊馬賽克拼圖,每次只能整塊換掉,那 StringBuilder 就像是磁鐵白板,你可以一直「貼」或「拿掉」那些拼圖,不用每次都換一塊新白板。
什麼時候該用 StringBuilder?
- 如果你要重複改變字串(尤其是在迴圈裡)。
- 如果你要從很多小片段組合成一大段文字。
- 如果你很在意速度或記憶體用量。
什麼時候不用?
- 如果你只做 1-2 個簡單的字串操作 —— 用一般 string 比較簡單又好讀。
- 如果你真的(不知道為什麼)想保留每個字串的狀態。
2. 建立和基本操作 StringBuilder
引用命名空間
開始用很簡單:別忘了加上命名空間。
using System.Text;// ← 一定要加!
建立新字串的方法
StringBuilder sb1 = new StringBuilder(); // 空的
StringBuilder sb2 = new StringBuilder("開始"); // 有初始文字
StringBuilder sb3 = new StringBuilder(100); // 預留 100 個字元
主要方法
var sb = new StringBuilder("開始");
sb.Append(" + 加了新文字"); // 加到最後面
sb.AppendLine("(這個會自動換行)"); // 加上 \n
sb.Insert(0, "起點: "); // 從第 0 個位置插入
sb.Remove(0, 7); // 從第 0 個位置刪掉 7 個字元
sb.Replace("文字", "STRING"); // 把所有 "文字" 換成 "STRING"
string result = sb.ToString(); // 取得最後的字串
3. StringBuilder 的效率
範例:效能比較
假設你要把 10 000 個數字串在一起(比如你要印出所有同學的學號):
天真做法(string)
string text = "";
for (int i = 0; i < 10000; i++)
{
text += i + " "; // 每次都創建新字串!
}
Console.WriteLine(text.Substring(0, 100) + "..."); // 只印前 100 個字元
最佳做法(StringBuilder)
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.Append(i).Append(' '); // 可以連著加!
}
Console.WriteLine(sb.ToString().Substring(0, 100) + "...");
差別在哪?
第一種會慢很多,也會吃掉更多記憶體。第二種又快又省記憶體。
4. StringBuilder 最常用的方法
Append —— 加入字串或其他型別(會自動轉成字串):
sb.Append("文字");
sb.Append(123); // 123 會變成 "123"
sb.Append('!'); // 字元
AppendLine —— 加入字串 + 換行符號:
sb.AppendLine("字串"); // 會加 "字串\n"
Insert —— 在指定位置插入字串:
sb.Insert(0, "Hello, "); // 插到最前面
Remove —— 刪掉幾個字元:
sb.Remove(3, 4); // 從第 3 個位置開始刪 4 個字元
Replace —— 把所有子字串或字元換掉:
sb.Replace("old", "new");
sb.Replace('a', 'A');
ToString —— 取得最終字串。
截斷字串
跟 string 不一樣,StringBuilder 物件可以直接設定新的長度,字串會被截斷到那裡。
sb.Length = 10; // 超過第 10 個字元的都會消失!
5. 使用細節和常見錯誤
為什麼 ToString() 很重要
你在用 StringBuilder 的時候,手上拿的不是字串,而是一個特殊物件。很多方法(像 Console.WriteLine)可以直接吃這個物件,因為它會自動轉成字串,但有時你會遇到驚喜 —— 如果你不小心跟一般字串比較,或像這樣:
if (sb == "哈囉") { ... } // 哎呀!這不會 work,就算 sb 裡面是 "哈囉"
結果會是 false:因為比較的是不同型別的物件,不是字串。
正確做法:
if (sb.ToString() == "哈囉") { ... }
可變性
跟 string 不一樣,StringBuilder 是 可變的(mutable)!同一個物件可以一直改內容,這就是它快的原因。
記得最後要用 ToString(),不然你拿到的會是像 System.Text.StringBuilder 這種奇怪的東西,不是字串。
緩衝區大小和自動擴充
StringBuilder 內部其實是存一個字元陣列。如果你一開始就知道要「搞」很長的字串,可以在建構子的時候給預期長度 —— 這樣會快一點。
var bigBuilder = new StringBuilder(50000); // 預計 5 萬個字元
但如果你估錯也沒關係!StringBuilder 會自己擴充緩衝區,只是會慢一點點。
記憶體爆掉?
理論上,如果你完全沒節制加了幾十億個字元,還是有可能遇到 OutOfMemoryException,但現實中幾乎不會發生啦。
GO TO FULL VERSION