+ 在迴圈裡會慢到爆?String 為不可變物件。s += "*" 實際上每次都會:
StringBuilder(或舊版 StringBuffer)。toString() 產生新 String。50,000 次迴圈 = 50,000 個中間物件,記憶體壓力大、GC 頻繁、效能指數級下降。
"*"。+、concat()、StringBuffer.append()、StringBuilder.append()。| 方式 | 平均耗時 (ms) | 備註 |
|---|---|---|
StringBuilder | ~1.8 | 非同步、無鎖,單執行緒首選 |
StringBuffer | ~2.0 | 同步(鎖),多執行緒安全但較慢 |
String.concat() | ~3.3 | 內部同樣用 StringBuilder,但每次新建實例 + toString() |
+ (迴圈) | ~1650+ | 每次新建 Builder,極度低效 |
concat() 內部其實也是 StringBuildernew StringBuilder() // 1. 新建
dup
invokespecial StringBuilder.
aload_1 // 載入原字串
invokevirtual StringBuilder.append(String)
ldc "*" // 載入常數
invokevirtual StringBuilder.append(String)
invokevirtual StringBuilder.toString() // 2. 轉回 String
astore_1 // 存回變數
可見 concat() 每次呼叫都 新建 StringBuilder → append 兩次 → toString,比直接重用同一個 StringBuilder 多出物件建立與方法呼叫開銷。
StringBuilder sb = new StringBuilder(); sb.append(...); 重用實例。StringBuffer(或 ThreadLocal 避免鎖)。+ 或 concat() 皆可,編譯器會優化成 StringBuilder,可讀性優先。String.join() / Collectors.joining(): 串流/集合拼接更優雅。String s = "a" + "b" + "c"; // 編譯期常數折疊 → "abc"
String s = a + b + c; // 執行期 → new StringBuilder().append(a).append(b).append(c).toString()
所以迴圈外的 + 沒有效能問題,問題在於迴圈內重複建立 Builder。
效能排序:StringBuilder > StringBuffer > concat() >> 迴圈內 +。
養成習慣:需要拼接時預設用 StringBuilder,除非確定多執行緒共享才用 StringBuffer,絕不在迴圈裡寫 s += ...。