1. 不可變的字串:朋友還是敵人?
在 Java 中,String 類別是不可變的(immutable)。這表示建立後的字串無法被更改。每次你「修改」字串,例如透過 + 或 concat() 加上內容,事實上都會建立一個新的物件,而舊的物件則等待垃圾回收。
範例:
String s = "Hello";
s = s + " world!";
System.out.println(s); // Hello world!
看起來好像變數 s 被修改了,但實際上是建立了新的字串 "Hello world!",而舊的 "Hello" 會留在記憶體中,直到被垃圾回收器清理。如果這類操作很多——例如在迴圈中——程式會變慢且耗用多餘記憶體。
想像你在堆疊積木,每次想加一塊新積木,都得把整座塔重建一遍。這可不太經濟,對吧?在頻繁修改的情況下,Java 中一般的 String 就是這樣運作的。
2. StringBuilder:快速的字串建構器
StringBuilder(位於 java.lang,無需匯入)是用於高效組裝與修改字串的工具。它是可變的(mutable):可以新增、刪除、插入字元與子字串,而不必為每次操作建立新物件。
類比:
如果 String 是一塊混凝土板,那麼 StringBuilder 就像 LEGO 積木:想加就加、想拆就拆,不必把整個結構重來。
如何建立 StringBuilder
StringBuilder sb = new StringBuilder(); // 空的
StringBuilder sb2 = new StringBuilder("初始值");
主要方法
| 方法 | 說明 | 使用範例 |
|---|---|---|
|
在末尾加入字串、數字、字元等 | |
|
在指定位置插入值 | |
|
刪除從 start(含)到 end(不含)的字元 | |
|
以其他內容取代部分字串 | |
|
將字串反轉 | |
|
轉換為一般字串 | |
|
將字串截斷或補齊至指定長度 | |
使用範例
StringBuilder sb = new StringBuilder();
sb.append("你好,");
sb.append("世界!");
System.out.println(sb); // 你好,世界!
sb.insert(7, "Java "); // 在「你好,」之後插入 "Java "
System.out.println(sb); // 你好,Java 世界!
sb.replace(8, 12, "其他"); // 把 "Java" 替換成「其他」
System.out.println(sb); // 你好,其他 世界!
sb.reverse();
System.out.println(sb); // 反轉後的內容
3. StringBuffer:具備多執行緒保護的老大哥
StringBuilder 與 StringBuffer 有什麼差別?
- StringBuilder 速度快,但非執行緒安全(未同步)。
- StringBuffer 較慢,但執行緒安全(已同步)。
如果你的應用只在單執行緒中運作——請使用 StringBuilder(更快)。若有多個執行緒可能同時修改同一段字串——請使用 StringBuffer。
4. 何時使用 StringBuilder 取代 String?
情境
- 在迴圈或組裝大型文字時,頻繁地對字串進行新增、刪除、插入。
- 從陣列/清單組裝字串(例如 CSV、HTML、報表等)。
- 在有大量字串操作的文字剖析與處理情境。
範例:從陣列組裝字串
不佳的方式(透過 String 與 +):
String[] names = {"Ivan", "Pyotr", "Maria"};
String result = "";
for (int i = 0; i < names.length; i++)
{
result += names[i];
if (i < names.length - 1)
{
result += ", ";
}
}
System.out.println(result);
較佳的方式(透過 StringBuilder):
String[] names = {"Ivan", "Pyotr", "Maria"};
StringBuilder sb = new StringBuilder();
for (int i = 0; i < names.length; i++)
{
sb.append(names[i]);
if (i < names.length - 1)
{
sb.append(", ");
}
}
System.out.println(sb.toString());
差異:第一種做法每一步都會建立新的字串;第二種做法則是持續擴充同一個 StringBuilder 物件。
5. 效能比較:String vs StringBuilder
// 使用 String
long t1 = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 10000; i++)
{
s += i + " ";
}
long t2 = System.currentTimeMillis();
System.out.println("String: " + (t2 - t1) + " 毫秒");
// 使用 StringBuilder
t1 = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++)
{
sb.append(i).append(" ");
}
s = sb.toString();
t2 = System.currentTimeMillis();
System.out.println("StringBuilder: " + (t2 - t1) + " 毫秒");
結論:在多次串接的情況下,StringBuilder 在多數情境通常快上數個數量級。
6. 實用細節
能否使用 String 的方法?
StringBuilder 有其專屬方法。若要取得字串,請呼叫 toString()。
StringBuilder sb = new StringBuilder("Hello");
String s = sb.toString(); // 現在 s 是一般的字串
可以用 equals 比較 StringBuilder 嗎?
注意:sb1.equals(sb2) 比較的是參考(位址),而不是內容。請這樣比較:
if (sb1.toString().equals(sb2.toString()))
{
// 內容相同
}
可以把 StringBuilder 傳給 System.out.println 嗎?
可以。System.out.println 會自動呼叫 toString()。
可以按索引讀取字元嗎?
可以,使用 charAt(int index),與一般字串相同。
7. 使用 StringBuilder 與 StringBuffer 的常見錯誤
錯誤 1:用 StringBuilder 的 equals 或運算子 == 來比較兩個物件。這些只會比較參考,不會比較內容。請使用 toString() 並比較字串。
錯誤 2:在需要 String 的地方(方法回傳、記錄、傳給 API)忘記呼叫 toString()。
錯誤 3:為了幾次簡單的串接就使用 StringBuilder。像 "Hello, " + name 這樣的寫法可讀性好且足夠高效。
錯誤 4:在迴圈裡用 + 串接 String。這在時間與記憶體上都不高效——請改用 StringBuilder。
錯誤 5:在沒有必要時混淆 StringBuffer 與 StringBuilder。若沒有多執行緒共同修改同一字串的需求——請選用 StringBuilder。
錯誤 6:不加思索地用 setLength() 截斷 StringBuilder 的內容。請務必確認新長度的正確性;超出新長度的資料會遺失。
GO TO FULL VERSION