1. 程式人生 > >Java程式碼優化建議

Java程式碼優化建議

總結日常Java開發常見優化策略,持續更新。

儘可能使用區域性變數

呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧中,速度較快,其他變數,如靜態變數、例項變數等,都在堆中建立,速度較慢。另外,棧中建立的變數,隨著方法的執行結束,這些內容就沒了,不需要額外的垃圾回收。

及時關閉流

Java程式設計過程中,進行資料庫連線、I/O流操作時務必小心,在使用完畢後,及時關閉以釋放資源。因為對這些大物件的操作會造成系統大的開銷,稍有不慎,將會導致嚴重的後果。而釋放資源最好的地方就是finally程式碼塊。

儘量減少對變數的重複計算

明確一個概念,對方法的呼叫,即使方法中只有一句語句,也是有消耗的,包括建立棧幀、呼叫方法時保護現場、呼叫方法完畢時恢復現場等。所以例如下面的操作:

for (int i = 0; i < list.size(); i++){
    ...
}

建議替換為:

for (int i = 0, length = list.size(); i < length; i++){
    ...
}

這樣,在list.size()很大的時候,就減少了很多的消耗

儘量採用懶載入的策略,即在需要的時候才建立

例如:

String str = "aaa";
if (i == 1){
  list.add(str);
}

建議替換為:

if (i == 1){
  String str = "aaa";
  list.add(str);
}

慎用異常

異常對效能不利。丟擲異常首先要建立一個新的物件,Throwable介面的建構函式呼叫名為fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,Java虛擬機器就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。

如果能估計到待新增的內容長度,為底層以陣列方式實現的集合、工具類指定初始長度

比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder為例:

  • StringBuilder()     // 預設分配16個字元的空間
  • StringBuilder(int size)  // 預設分配size個字元的空間
  • StringBuilder(String str) // 預設分配16個字元+str.length()個字元空間

可以通過類(這裡指的不僅僅是上面的StringBuilder)的建構函式來設定它的初始化容量,這樣可以明顯地提升效能。比如StringBuilder吧,length表示當前的StringBuilder能保持的字元數量。因為當StringBuilder達到最大容量的時候,它會將自身容量增加到當前的2倍再加2,無論何時只要StringBuilder達到它的最大容量,它就不得不建立一個新的字元陣列然後將舊的字元陣列內容拷貝到新字元陣列中----這是十分耗費效能的一個操作。試想,如果能預估到字元陣列中大概要存放5000個字元而不指定長度,最接近5000的2次冪是4096,每次擴容加的2不管,那麼:

  • 在4096 的基礎上,再申請8194個大小的字元陣列,加起來相當於一次申請了12290個大小的字元陣列,如果一開始能指定5000個大小的字元陣列,就節省了一倍以上的空間
  • 把原來的4096個字元拷貝到新的的字元陣列中去

這樣,既浪費記憶體空間又降低程式碼執行效率。所以,給底層以陣列實現的集合、工具類設定一個合理的初始化容量是錯不了的,這會帶來立竿見影的效果。但是,注意,像HashMap這種是以陣列+連結串列實現的集合,別把初始大小和你估計的大小設定得一樣,因為一個table上只連線一個物件的可能性幾乎為0。初始大小建議設定為2的N次冪,如果能估計到有2000個元素,設定成new HashMap(128)、new HashMap(256)都可以。

當複製大量資料時,使用System.arraycopy()命令

乘法和除法使用移位操作

例如:

for (val = 0; val < 100000; val += 5){
  a = val * 8;
  b = val / 2;
}

用移位操作可以極大地提高效能,因為在計算機底層,對位的操作是最方便、最快的,因此建議修改為:

for (val = 0; val < 100000; val += 5){
  a = val << 3;
  b = val >> 1;
}

移位操作雖然快,但是可能會使程式碼不太好理解,因此最好加上相應的註釋。

迴圈內不要不斷建立物件引用

例如:

for (int i = 1; i <= count; i++){
    Object obj = new Object();    
}

這種做法會導致記憶體中有count份Object物件引用存在,count很大的話,就耗費記憶體了,建議為改為:

Object obj = null;
for (int i = 0; i <= count; i++){
    obj = new Object();
}

這樣的話,記憶體中只有一份Object物件引用,每次new Object()的時候,Object物件引用指向不同的Object罷了,但是記憶體中只有一份,這樣就大大節省了記憶體空間了。

基於效率和型別檢查的考慮,應該儘可能使用array,無法確定陣列大小時才使用ArrayList

儘量使用HashMap、ArrayList、StringBuilder,除非執行緒安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,後三者由於使用同步機制而導致了效能開銷

儘量避免隨意使用靜態變數

要知道,當某個物件被定義為static的變數所引用,那麼gc通常是不會回收這個物件所佔有的堆記憶體的,如:

public class A{
    private static B b = new B();  
}

此時靜態變數b的生命週期與A類相同,如果A類不被解除安裝,那麼引用B指向的B物件會常駐記憶體,直到程式終止

及時清除不再需要的會話

為了清除不再活動的會話,許多應用伺服器都有預設的會話超時時間,一般為30分鐘。當應用伺服器需要儲存更多的會話時,如果記憶體不足,那麼作業系統會把部分資料轉移到磁碟,應用伺服器也可能根據MRU(最近最頻繁使用)演算法把部分不活躍的會話轉儲到磁碟,甚至可能丟擲記憶體不足的異常。如果會話要被轉儲到磁碟,那麼必須要先被序列化,在大規模叢集中,對物件進行序列化的代價是很昂貴的。因此,當會話不再需要時,應當及時呼叫HttpSession的invalidate()方法清除會話。

參考文章:

https://www.cnblogs.com/xrq730/p/4865416.html
更多資訊可以關注我的個人部落格:逸竹小站或逸竹小站

也歡迎關注我的公眾號:yizhuxiaozhan,二維碼: