[java基礎]11個簡單的Java效能調優技巧,傻瓜都能學會

加瓦一枚發表於2019-03-28

大多數開發人員理所當然地以為效能優化很複雜,需要大量的經驗和知識。好吧,不能說這是完全錯誤的。優化應用程式以獲得最佳效能不是一件容易的事情。但是,這並不意味著如果你不具備這些知識,就不能做任何事情。

 

這裡有11個易於遵循的建議和最佳實踐可以幫助你建立一個效能良好的應用程式。

大部分建議是針對Java的。但也有若干建議是與語言無關的,可以應用於所有應用程式和程式語言。在討論專門針對Java的效能調優技巧之前,讓我們先來看看通用技巧。

1.在你知道必要之前不要優化

這可能是最重要的效能調整技巧之一。你應該遵循常見的最佳實踐做法並嘗試高效地實現用例。但是,這並不意味著在你證明必要之前,你應該更換任何標準庫或構建複雜的優化。

在大多數情況下,過早優化不但會佔用大量時間,而且會使程式碼變得難以閱讀和維護。更糟糕的是,這些優化通常不會帶來任何好處,因為你花費大量時間來優化的是應用程式的非關鍵部分。

那麼,你如何證明你需要優化一些東西呢?

首先,你需要定義應用程式程式碼的速度得多快,例如,為所有API呼叫指定最大響應時間,或者指定在特定時間範圍內要匯入的記錄數量。在完成這些之後,你就可以測量應用程式的哪些部分太慢需要改進。然後,接著看第二個技巧。

2.使用分析器查詢真正的瓶頸

在你遵循第一個建議並確定了應用程式的某些部分需要改進後,那麼從哪裡開始呢?

你可以用兩種方法來解決問題:

  • 檢視你的程式碼,並從看起來可疑或者你覺得可能會產生問題的部分開始。

  • 或者使用分析器並獲取有關程式碼每個部分的行為和效能的詳細資訊。

希望不需要我解釋為什麼應該始終遵循第二種方法的原因。

很明顯,基於分析器的方法可以讓你更好地理解程式碼的效能影響,並使你能夠專注於最關鍵的部分。如果你曾使用過分析器,那麼你一定記得曾經你是多麼驚訝於一下就找到了程式碼的哪些部分產生了效能問題。老實說,我第一次的猜測不止一次地導致我走錯了方向。

3.為整個應用程式建立效能測試套件

這是另一個通用技巧,可以幫助你避免在將效能改進部署到生產後經常會發生的許多意外問題。你應該總是定義一個測試整個應用程式的效能測試套件,並在效能改進之前和之後執行它。

這些額外的測試執行將幫助你識別更改的功能和效能副作用,並確保不會導致弊大於利的更新。如果你工作於被應用程式若干不同部分使用的元件,如資料庫或快取,那麼這一點就尤其重要。

4.首先處理最大的瓶頸

在建立測試套件並使用分析器分析應用程式之後,你可以列出一系列需要解決以提高效能的問題。這很好,但它仍然不能回答你應該從哪裡開始的問題。你可以專注於速效方案,或從最重要的問題開始。Java 程式設計師必須清楚的 7 個效能指標,這個你也必須會。

速效方案一開始可能會很有吸引力,因為你可以很快顯示第一個成果。但有時,可能需要你說服其他團隊成員或管理層認為效能分析是值得的——因為暫時看不到效果。

但總的來說,我建議首先處理最重要的效能問題。這將為你提供最大的效能改進,而且可能再也不需要去解決其中一些為了滿足效能需求的問題。

常見的效能調整技巧到此結束。下面讓我們仔細看看一些特定於Java的技巧。

5.使用StringBuilder以程式設計方式連線String

有很多不同的選項來連線Java中的String。例如,你可以使用簡單的+或+ =,以及StringBuffer或StringBuilder。String 真的是不可變的嗎?

那麼,你應該選擇哪種方法?

答案取決於連線String的程式碼。如果你是以程式設計方式新增新內容到String中,例如在for迴圈中,那麼你應該使用StringBuilder。它很容易使用,並提供比StringBuffer更好的效能。但請記住,與StringBuffer相比,StringBuilder不是執行緒安全的,可能不適合所有用例。StringBuffer 和 StringBuilder 的 3 個區別,這個你必須清楚。

你只需要例項化一個新的StringBuilder並呼叫append方法來向String中新增一個新的部分。在你新增了所有的部分之後,你就可以呼叫toString()方法來檢索連線的String。

下面的程式碼片段顯示了一個簡單的例子。在每次迭代期間,這個迴圈將i轉換為一個String,並將它與一個空格一起新增到StringBuilder sb中。所以,最後,這段程式碼將在日誌檔案中寫入“This is a test0 1 2 3 4 5 6 7 8 9”。

 

StringBuilder sb = new StringBuilder(“This is a test”);
for (int i=0; i<10; i++) {
sb.append(i);
sb.append(” “);
}
log.info(sb.toString());

正如在程式碼片段中看到的那樣,你可以將String的第一個元素提供給構造方法。這將建立一個新的StringBuilder,新的StringBuilder包含提供的String和16個額外字元的容量。當你向StringBuilder新增更多字元時,JVM將動態增加StringBuilder的大小。

如果你已經知道你的String將包含多少個字元,則可以將該數字提供給不同的構造方法以例項化具有定義容量的StringBuilder。這進一步提高了效率,因為它不需要動態擴充套件其容量。

6.使用+連線一個語句中的String

當你用Java實現你的第一個應用程式時,可能有人告訴過你不應該用+來連線String。如果你是在應用程式邏輯中連線字串,這是正確的。字串是不可變的,每個字串的連線結果都儲存在一個新的String物件中。這需要額外的記憶體,會減慢你的應用程式,特別是如果你在一個迴圈內連線多個字串的話。

在這些情況下,你應該遵循技巧5並使用StringBuilder。

但是,如果你只是將字串分成多行來改善程式碼的可讀性,那情況就不一樣了。

Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);

在這些情況下,你應該用一個簡單的+來連線你的字串。Java編譯器會對此優化並在編譯時執行連線。所以,在執行時,你的程式碼將只使用1個String,不需要連線。

7.儘可能使用基元

避免任何開銷並提高應用程式效能的另一個簡便而快速的方法是使用基本型別而不是其包裝類。所以,最好使用int來代替Integer,使用double來代替Double。這允許JVM將值儲存在堆疊而不是堆中以減少記憶體消耗,並作出更有效的處理。

8.試著避免BigInteger和BigDecimal

既然我們在討論資料型別,那麼我們也快速瀏覽一下BigInteger和BigDecimal吧。尤其是後者因其精確性而受到大家的歡迎。但是這是有代價的。

BigInteger和BigDecimal比簡單的long或double需要更多的記憶體,並且會顯著減慢所有計算。所以,你如果需要額外的精度,或者數字將超過long的範圍,那麼最好三思而後行。這可能是你需要更改以解決效能問題的唯一方法,特別是在實現數學演算法的時候。金融系統中正確的金額計算及儲存方式,這個你瞭解下。

9.首先檢查當前日誌級別

這個建議應該是顯而易見的,但不幸的是,很多程式設計師在寫程式碼的時候都會大多會忽略它。在你建立除錯訊息之前,始終應該首先檢查當前日誌級別。否則,你可能會建立一個之後會被忽略的日誌訊息字串。

這裡有兩個反面例子。

// don’t do this
log.debug(“User [” + userName + “] called method X with [” + i + “]”);

// or this
log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));

在上面兩種情況中,你都將執行建立日誌訊息所有必需的步驟,在不知道日誌框架是否將使用日誌訊息的前提下。因此在建立除錯訊息之前,最好先檢查當前的日誌級別。

// do this
if (log.isDebugEnabled()) { 
  log.debug(“User [” + userName + “] called method X with [” + i + “]”);
}

10.使用Apache Commons StringUtils.Replace而不是String.replace

一般來說,String.replace方法工作正常,效率很高,尤其是在使用Java 9的情況下。但是,如果你的應用程式需要大量的替換操作,並且沒有更新到最新的Java版本,那麼我們依然有必要查詢更快和更有效的替代品。

有一個備選答案是Apache Commons Lang的StringUtils.replace方法。正如Lukas Eder在他最近的一篇部落格文章中所描述的,StringUtils.replace方法遠勝Java 8的String.replace方法。

而且它只需要很小的改動。即新增Apache Commons Lang專案的Maven依賴項到應用程式pom.xml中,並將String.replace方法的所有呼叫替換為StringUtils.replace方法。

// replace this
test.replace(“test”, “simple test”);

// with this
StringUtils.replace(test, “test”, “simple test”);

11.快取昂貴的資源,如資料庫連線

快取是避免重複執行昂貴或常用程式碼片段的流行解決方案。總的思路很簡單:重複使用這些資源比反覆建立新的資源要便宜。

一個典型的例子是快取池中的資料庫連線。新連線的建立需要時間,如果你重用現有連線,則可以避免這種情況。

你還可以在Java語言本身找到其他例子。例如,Integer類的valueOf方法快取了-128到127之間的值。你可能會說建立一個新的Integer並不是太昂貴,但是由於它經常被使用,以至於快取最常用的值也可以提供效能優勢。

但是,當你考慮快取時,請記住快取實現也會產生開銷。你需要花費額外的記憶體來儲存可重用資源,因此你可能需要管理快取以使資源可訪問,以及刪除過時的資源。

所以,在開始快取任何資源之前,請確保實施快取是值得的,也就是說必須足夠多地使用它們。

總結

正如你所看到的,有時不需要太多工作就可以提高應用程式的效能。本文中的大部分建議只需要你稍作努力就可以將它們應用於你的程式碼。

但是,最重要的還是那些與是什麼程式語言無關的技巧:

  • 在你知道必要之前不要優化

  • 使用分析器查詢真正的瓶頸

  • 首先處理最大的瓶頸

相關文章