Java 降低了開發門檻,但若缺乏良好的編碼習慣,效能往往不如 C++。大多數效能問題源於程式碼本身而非語言。本文聚焦兩個高頻使用且易被濫用的類別:java.lang.String 與 java.util.Vector,透過實例剖析優化技巧。
String s1 = "Testing String";
String s2 = "Concatenation Performance";
String s3 = s1 + " " + s2; // 效率低?
編譯器實際會將其轉換為 StringBuffer(或 JDK 5+ 的 StringBuilder)操作,因此手動寫 StringBuffer 並不一定更快:
StringBuffer sb = new StringBuffer();
sb.append("Testing String");
sb.append(" ");
sb.append("Concatenation Performance");
String s3 = sb.toString();
StringBuffer() 預設容量僅 16 字元。當內容超過容量時,會觸發 expandCapacity(),內容複製開銷大。若能預估最終長度,指定初始容量可避免多次擴容:
// 最終約 40 字元,給 45 餘裕
StringBuffer sb = new StringBuffer(45);
sb.append("Testing String");
sb.append(" ");
sb.append("Concatenation Performance");
String s3 = sb.toString();
// 低效:每次迴圈建立新 StringBuffer 與 String
String s = "";
for (int i = 1; i < 10; i++) {
s = s + "+" + i;
}
// 高效:複用單一 StringBuffer
StringBuffer sb = new StringBuffer();
for (int i = 1; i < 10; i++) {
sb.append(i).append("+");
}
String s = sb.append("=").append(sum).toString();
前者每次迴圈產生兩個臨時物件,後者零臨時物件,效能差距隨迴圈次數線性放大。
Vector 底層為陣列,add(0, obj) 會導致所有現有元素後移,若在迴圈中重複執行,時間複雜度達 O(n²):
Vector v = new Vector(100000);
for (int i = 0; i < 100000; i++) {
v.add(0, obj); // 極慢:每次搬移所有元素
}
改用 add(obj) 尾端新增,無搬移開銷,快數個數量級:
for (int i = 0; i < 100000; i++) {
v.add(obj); // 僅擴容時複製
}
remove(0):後續元素前移,慢。remove(size()-1):尾端刪除,無搬移,快。removeAllElements() 一次完成,避免迴圈開銷。// 低效:indexOf 與 remove 各搜尋一次
String s = "Hello";
int i = v.indexOf(s);
if (i != -1) v.remove(s); // remove 內部再搜尋
// 優化:直接用索引刪除
if (i != -1) v.remove(i);
// 最佳:直接 remove 物件,內部僅搜尋一次
v.remove(s);
// 低效:每次迴圈呼叫 size()
for (int i = 0; i < v.size(); i++) { ... }
// 優化:將 size 存入區域變數
int size = v.size();
for (int i = 0; i < size; i++) { ... }
雖 size() 極簡單,但方法呼叫仍有堆疊幀開銷,迴圈次數大時差距可觀。
StringBuffer/StringBuilder 容量。removeAllElements)、迴圈邊界條件提取不變量。