代碼級別的優化
負載測試和應用程序監控對於確定應用程序的一些關鍵性能瓶頸非常有用。但同時,我們需要遵循良好的編碼習慣,以避免在對應用程序進行監控的時候出現過多的性能問題。
在下一章節中,我們將來看一些最佳實踐。
使用StringBuilder來連接字符串
字符串連接是一個非常常見的操作,也是一個低效率的操作。簡單地說,使用+=來追加字符串的問題在於每次操作都會分配新的String。
下面這個例子是一個簡化了的但卻很典型的循環。前面使用了原始的連接方式,後面使用了構建器:
public String stringAppendLoop() { String s = ""; for (int i = 0; i < 10000; i++) { if (s.length() > 0) s += ", "; s += "bar"; } return s; }public String stringAppendBuilderLoop() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { if (sb.length() > 0) sb.append(", "); sb.append("bar"); } return sb.toString(); }
上面代碼中使用的StringBuilder對性能的提升非常有效。請注意,現代的JVM會在編譯或者運行時對字符串操作進行優化。
避免遞歸
導致出現StackOverFlowError錯誤的遞歸代碼邏輯是Java應用程序中另一種常見的問題。如果無法去掉遞歸邏輯,那麼尾遞歸作爲替代方案將會更好。
我們來看一個頭遞歸的例子:
public int factorial(int n) { if (n == 0) { return 1; } else { return n * factorial(n - 1); } }
現在我們把它重寫爲尾遞歸:
private int factorial(int n, int accum) { if (n == 0) { return accum; } else { return factorial(n - 1, accum * n); } }public int factorial(int n) { return factorial(n, 1); }
其他JVM語言(如Scala)已經在編譯器級支持尾遞歸代碼的優化,當然,對於這種優化目前也存在着一些爭議。
謹慎使用正則表達式
正則表達式在很多場景中都非常有用,但它們往往具有非常高的性能成本。瞭解各種使用正則表達式的JDK字符串方法很重要,例如String.replaceAll()、String.split()。
如果你不得不在計算密集的代碼段中使用正則表達式,那麼需要緩存Pattern的引用而避免重複編譯:
static final Pattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");
使用一些流行的庫,比如Apache Commons Lang也是一個很好的選擇,特別是在字符串的操作方面。
避免創建和銷燬過多的線程
線程的創建和處置是JVM出現性能問題的常見原因,因爲線程對象的創建和銷燬相對較重。
如果應用程序使用了大量的線程,那麼使用線程池會更加有用,因爲線程池允許這些昂貴的對象被重用。
爲此,Java的ExecutorService是線程池的基礎,它提供了一個高級API來定義線程池的語義並與之進行交互。
Java 7中的Fork/Join框架也值得提一下,因爲它提供了一些工具來嘗試使用所有可用的處理器核心以幫助加速並行處理。爲了提高並行執行效率,框架使用了一個名爲ForkJoinPool的線程池來管理工作線程。