效能優化非玄學,而是建立在 JVM 執行模型與硬體特性之上的工程取捨。本文整理五大高頻優化方向,附程式碼對比,適合作為 Code Review 核對清單。
for (int i = 0; i < list.size(); i++) { ... }
for (int i = 0, n = list.size(); i < n; i++) { ... }
JIT 可能內聯 size(),但顯式快取零成本且語意清晰。
for (int i = 0; i < x.length; i++) {
X[i] = x[i] / scaleA * scaleB; // 每次做除法與乘法
}
double scale = (double) scaleB / scaleA; // 注意型別提升避免整數除法截斷
for (int i = 0; i < x.length; i++) {
X[i] = x[i] * scale;
}
String s = "";
for (String part : parts) s += part; // 每次產生新 String + StringBuilder + toString()
// 預估總長度避免擴容複製
StringBuilder sb = new StringBuilder(estimatedTotalLength);
for (String part : parts) sb.append(part);
String result = sb.toString();
補充:Java 9+ String.join()、StringJoiner 內部同樣用 StringBuilder,語意更清楚。
int/long/double 而非 Integer/Long/Doublestatic final:編譯期常數摺疊,執行期零開銷try { return map.get(key); } catch (NullPointerException e) { return defaultValue; }
Value v = map.get(key);
return (v != null) ? v : defaultValue;
原因:建立異常需捕獲堆疊快照,成本極高(微秒級 vs 納秒級)。
// 差:每個呼叫獨立 try-catch
try { foo(); } catch (...) {}
try { bar(); } catch (...) {}
// 好:單一 try-catch 包多個呼叫
try {
foo();
bar();
} catch (Exception e) { ... }
System.nanoTime() 迴圈極易誤導OutputStream.write(bytes) vs PrintWriter.print(string) 哪個快?只能在目標環境實測才知道。| 面向 | 核心原則 | 工具/檢查點 |
|---|---|---|
| 迴圈 | 不變量外提、長度快取、減少方法呼叫 | JIT 日誌 (-XX:+PrintCompilation) |
| 字串 | 拼接用 StringBuilder、預估容量 | GC 日誌觀察暫存物件量 |
| 資料型別 | 熱點路徑用基本型別、常數 static final | JOL (Java Object Layout) 觀察物件大小 |
| 異常 | 僅用於異常狀態、合併 catch 區塊 | 異常堆疊追蹤開銷分析 |
| 驗證 | 無 JMH 測試不優化、目標環境實測 | JMH、Async Profiler、perf |
優化順序永遠是:正確性 → 可讀性 → 架構層面優化(演算法、快取、非同步)→ 微觀代碼優化。本文技巧屬於最後一層,若架構有瓶頸(如 N+1 查詢、同步阻塞),微優化效果微乎其微。先測量,再動刀,才是專業態度。