如何編寫高性能Java程序的技術匯編?
本文搜集整理了40多條編寫高性能Java代碼的心法,希望可以幫到您。
1.清理代碼并修改算法
不斷地重構和整理代碼,去除算法中冗余的步驟,使代碼更加可讀和精煉的同時,通過避免額外的計算來提升性能。
2.盡量指定類、方法為final
Java編譯器會盡量將final的方法內聯化,從而大幅提升性能(約50%)。所以,如可能,盡量將對類、方法使用final修飾符。
3.避免濫用靜態變量
如無必要,避免將成員申明為static,否則可能導致該成員持有的對象一直不會被釋放,直到程序終止或修改引用。
4.保持方法簡短
過大的方法將消耗更多的內存和CPU周期,而短小精悍的方法將帶來更好的內聚性、可讀性,和更小的內存占用,從而帶來更好的性能。嘗試在適當的邏輯點,將過大的方法拆分為合適的小方法。
5.避免使用遞歸
遞歸是一種很好的算法,有切實的好處和實際用途,但是遞歸本身具有較高的運行成本。如果性能成為問題,可以考慮放棄遞歸,改用循環。
6.避免濫用異常
拋出異常需要創建對象、獲取堆棧等較重開銷,異常只用來處理錯誤,不要刻意用于控制運行流程。
7.盡量使用局部變量
調用方法傳入的參數、方法創建的臨時變量等局部變量都存于棧中,訪問時比存于堆中的靜態變量、實例變量要快,而且也會隨著方法調用結束而被快速清理掉。
8.減少對變量的重復計算
調用方法存在一定成本,如創建棧幀、保護現場、恢復現場等,所以,如果變量只需計算一次,就可以提前計算好,不要重復計算,減少方法調用。
9.盡量使用基礎類型
將數據存儲在棧,而不是堆上,有利于更快地回收內存空間,更快地訪問數據。因此,盡量使用基礎類型(如int),而不是包裝類型(Integer),有助于降低內存消耗,提升性能。
如果可能,盡量使用array,而不是ArrayList。
10.避免使用BigInteger和BigDecimal
BigInteger和BigDecimal提供了更高的計算精度,但是也會占用更多的內存和CPU,如果允許的話,使用Long和Double作為替代。
11.使用isEmpty檢查String是否為空
String是一個byte數組,isEmpty會判斷數組的length,很快就能獲得結果。
12.對于單字符,使用char類型,而不是String
在處理單個字符的字符串時,使用char以獲得更好的性能。
13.使用更輕量級運算
必要時,乘除運算可使用更輕量的位移運算替代。
14.避免Random實例被多線程使用
多線程共用Random實例時,容易因競爭seed而性能下降,可以使用ThreadLocalRandom作為替代。
15.使用StringBuilder拼接字符串
使用+號拼接字符串,可能會創建過多的字符串對象。而StringBuffer有同步機制,性能不及StringBuilder,但要注意StringBuilder是線程不安全的。
在單個語句中使用+連接長字符串,以提升可讀性,也是可以被編譯器優化的。
避免在循環體中使用+拼接字符串,這樣可能會導致創建過多的StringBuilder對象。
16.更好地替換字符串
在Java8及以下版本中,使用Apache Commons StringUtils.replace來替換字符串,比JDK原生的String.replace高效,而更高版本的Java,則使用String.replace會更好。
17.避免過多if-else語句
在代碼中,尤其是循環體中過多地使用if-else語句,將迫使JVM比較這些條件,從而消耗性能。如果業務邏輯中的判斷條件過多,可嘗試通過分組并計算boolean結果,再在if語句中使用它。
18.盡可能復用已有對象,而不是隨意創建
創建新對象有一定的成本,尤其是大量創建的情況下。如果可以,盡量復用已有對象,甚至使用單例模式,而不是隨意創建新的對象,尤其是那些大的對象。
如果存在較多的重復字符串,可以考慮創建這些字符串對象時使用String.intern方法,將字符串放入常量池,再次創建時從常量池獲取同一對象,以減少內存占用。
在滿足某些條件時,才使用的對象,可以考慮延遲到進入條件區時再創建,而不是提早創建。
避免在循環中聲明和創建對象,創建過多的對象引用,可以把聲明拿到循環之前。
19.盡量使用indexOf,而不是split分割字符串
String的split方法中使用了功能更強大的正則表達式,但正則表達式的性能并不總是好,使用不當會導致回溯問題,使CPU居高不下。所以,除非回溯問題可控,盡量使用indexOf來分割字符串。
在任何時候使用正則表達式時,都要盡量采用獨占模式,避免使用貪婪模式,避免分支選擇,避免捕獲組的嵌套等,以避免性能問題。
20.選擇合適的集合類型
ArrayList、HashMap不是線程安全的,而Vector、Hashtable是線程安全的同步集合。當在多線程環境下可能需要線程安全的集合,而不需要線程同步時,盡量使用ArrayList、HashMap,以獲得多倍的性能提升。
ArrayList和LinkedList分別基于數組和鏈表實現,兩者的利用迭代器遍歷(含foreach)的性能相當,但使用for循環遍歷時,ArrayList因為有快速隨機訪問的特性,性能要高于LinkedList。另外,在ArrayList不發生擴容的情況下,在尾部添加刪除元素的性能要略高于LinkedList,而LinkedList在頭部添加元素的性能較ArrayList高。
21.在構造時初始化集合
如果集合內元素可在初始化時確定,則盡量在集合對象構造時初始化進去,而不是先實例化集合對象,再一個一個將元素添加進去。
22.高效使用HashMap
在已知數據量的情況下,提前設置初始容量(數據量 ÷ 加載因子),避免擴容開銷。
一般使用默認的加載因子(0.75)即可,當特別要求充分利用內存資源時,可增大加載因子,而當查詢類操作頻繁時,可考慮縮減加載因子。
23.不要在循環中獲取集合的大小
如果要遍歷集合,那么就提前獲取集合的長度,而不是在循環中每次判斷集合的大小。
24.盡量減少集合方法的調用次數
集合提供了很多好用的方法,如size()、containsKey()等,如有可能,盡量減少和避免調用這些方法
25.使用addAll,而不是add
addAll可以筆add擁有每秒更高的操作數,如果可以,在向ArrayList這種集合添加多個元素時,盡量使用addAll批量添加,而不是通過add方法逐個添加。
26.使用entrySet,而不是keySet
EntrySet 可以在一秒鐘內比 KeySet 多運行 9000 個操作,所以在遍歷HashMap時,盡量使用entrySet,而不是keySet。
27.使用singletonList構造單元素集合
使用singletonList生成單元素集合,比用構造函數new一個更好。
28.使用EnumSet,而不是HashSet
如果Set中存儲的時枚舉值(Enum),則最好使用EnumSet,以獲得更好的性能。
29.謹慎使用ArrayList的contains方法
集合大都有一個contains方法,用于判斷元素是否已存在于集合中。但是,當ArrayList、Vector這種集合數據量較大時,此方法在最差情況下將遍歷整個集合,如果在循環中使用,將帶來巨大的性能開銷。因此,當需要在大型數據集中搜索時,可考慮使用HashMap替代ArrayList。
30.必要時使用Stream遍歷集合
通常,在數量量少、循環次數較少,以及應用在單核運行的情況下,常規迭代遍歷集合的性能較高。如果在多核環境下,對大數據量集合進行迭代,則可以考慮使用由并行機制的Stream。
31.必要時使用NIO
傳統I/O類在高并發、大數據場景下容易發生阻塞,而且數據在內核空間和用戶空間的復制也存在性能開銷。一般的場景,可通過Buffer解決阻塞問題,而性能要求更高時,就需要引入NIO,通過DirectBuffer、Channel、多路復用等提升性能。
32.必要時使用高性能通信協議
通常,在需要高性能的分布式環境中,應用服務間調用會采用RPC通信框架,而RMI這種較早的RPC通信方式因序列化性能差、阻塞式I/O、短連接等弊病導致性能不能滿足需要。為此,可改為使用Dubbo這種在各方面優化過的通信協議,以滿足高并發、小對象傳輸需要。
33.必要時使用序列化框架
Java原生的序列化性能較差,如有必要,可使用Protobuf、FastJson、Kryo等序列化框架進行安全、高效地序列化。
34.謹慎使用字符串作為同步對象
JVM會緩存字符串對象,不同地方聲明的String,如果未使用new String,則可能指向同一個字符串對象,如果使用它作為同步對象,則可能產生意想不到的后果。另外,作為一條通用規則,請盡量保持同步塊內的處理量為最低限度,以提升性能。
35.多線程調優技術
對于多線程運行的代碼,可考慮以下調優方法:
- 在邏輯簡單、運算較快的情況下優先考慮單線程模式,而耗時的復雜計算則可以考慮多線程并發
- 降低鎖的粒度,縮小同步塊,分離讀寫鎖,最小化鎖的持有時間,以減少鎖競爭,提升自旋鎖成功率,降低升級為重量級鎖的可能性
- 僅需要保障可見性、有序性時,可使用開銷較小的volatile關鍵字,避免上下文切換
- 設置合理的線程池大小,避免過多線程的上下文切換開銷
- 適時使用并發容器類,對數據有強一致性要求時,可使用Hashtable、Vector等強一致性容器,而讀遠大于寫時可使用CopyOnWriteArrayList,當數據量較小、查詢頻繁時,可使用ConcurrentHashMap容器類。
36.輸出前檢查日志級別
在輸出某個級別的日志之前,先檢查該級別是否開啟,以避免額外的計算。
37.避免將大對象輸出到日志中
在將對象輸出到日志時,僅選取關鍵的屬性作為輸出,避免將整個大對象都序列化輸出。
38.緩存昂貴的資源
創建對象有性能開銷,有些對象(如數據庫連接)的創建過程尤其耗時,而有些對象本身則占用較大資源,將這些對象緩存起來以便再次使用,而不是隨意創建,將大大提升性能。
如果可以,盡量使用類似Integer.valueOf方法復用內存中已有對象,而不是用new Integer創建新的對象,尤其是需要大量這種對象的時候。
但是,需要注意,緩存需要額外進行管理,如果合算才使用。
39.使用預編譯的SQL語句
預編譯語句(PreparedStatement)具有編譯一次而多次使用的特點,比起普通Statement有更好的性能。預編譯語句也是安全的,可以避免SQL注入。
40.只選取需要的數據列,而不是*
SELECT語句中,只返回那些需要的列,可以節省網絡帶寬,帶來更好的性能。
41.使用正確的表聯接
當需要從多個表關聯查詢數據時,注意使用正確的表聯接方式,并在聯接字段上建立必要的索引。
42.使用聯接,而不是子查詢
相對于聯接只遍歷一次數據,子查詢會多次遍歷數據,比聯接耗費更多的時間。
43.使用存儲過程,而不是查詢
如果查詢語句過于復雜而冗長,則考慮改為使用數據庫存儲過程,以獲得存儲過程預編譯、較少的數據傳遞等帶來的性能。