Java代碼優化

爲何要Java代碼優化?java

 

代碼優化的最重要的做用應該是:避免未知的錯誤。在代碼上線運行的過程當中,每每會出現不少咱們意想不到的錯誤,由於線上環境和開發環境是很是不一樣的,錯誤定位到最後每每是一個很是小的緣由。所以,在寫代碼的時候,從源頭開始注意各類細節,權衡並使用最優的選擇,將會很大程度上避免出現未知的錯誤,從長遠看也極大的下降了工做量。算法

 

代碼優化的目標是:數據庫

  • 減少代碼的體積編程

  • 提升代碼運行的效率數組

     

 

代碼優化細節安全

 

(1)儘可能指定類、方法的final修飾符服務器

 

帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String,整個類都是final的。爲類指定final修飾符可讓類不能夠被繼承,爲方法指定final修飾符可讓方法不能夠被重寫。若是指定了一個類爲final,則該類全部的方法都是final的。Java編譯器會尋找機會內聯全部的final方法,內聯對於提高Java運行效率做用重大,具體參見Java運行期優化。此舉可以使性能平均提升50%。多線程

 

(2)儘可能重用對象併發

 

特別是String對象的使用,出現字符串鏈接時應該使用StringBuilder/StringBuffer代替。因爲Java虛擬機不只要花時間生成對象,之後可能還須要花時間對這些對象進行垃圾回收和處理,所以,生成過多的對象將會給程序的性能帶來很大的影響。dom

 

(3)儘量使用局部變量

 

調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧中,速度較快,其餘變量,如靜態變量、實例變量等,都在堆中建立,速度較慢。另外,棧中建立的變量,隨着方法的運行結束,這些內容就沒了,不須要額外的垃圾回收。

 

(4)及時關閉流

 

Java編程過程當中,進行數據庫鏈接、I/O流操做時務必當心,在使用完畢後,及時關閉以釋放資源。由於對這些大對象的操做會形成系統大的開銷,稍有不慎,將會致使嚴重的後果。

 

 

(5)儘可能減小對變量的重複計算

 

明確一個概念,對方法的調用,即便方法中只有一句語句,也是有消耗的,包括建立棧幀、調用方法時保護現場、調用方法完畢時恢復現場等。因此例以下面的操做:

 

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

{...}

 

建議替換爲:

 

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

{...}

 

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

 

(6)儘可能採用懶加載的策略,即在須要的時候才建立

 

例如:

 

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

建議替換爲:

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

 (7)慎用異常

異常對性能不利。拋出異常首先要建立一個新的對象,Throwable接口的構造函數調用名爲 fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,由於在處理過程當中建立了一個新的對象。異常只能用於錯誤處理,不該該用來控制程序流程。

 

(8)不要在循環中使用try…catch…,應該把其放在最外層

 

根據網友們提出的意見,這一點我認爲值得商榷

 

(9)若是能估計到待添加的內容長度,爲底層以數組方式實現的集合、工具類指定初始長度

 

好比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)均可以。

 

(10)當複製大量數據時,使用 System.arraycopy()命令

 

(11)乘法和除法使用移位操做

 

例如:

 

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;
}

 

移位操做雖然快,可是可能會使代碼不太好理解,所以最好加上相應的註釋。

 

(12)循環內不要不斷建立對象引用

 

例如:

 

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罷了,可是內存中只有一份,這樣就大大節省了內存空間了。

(13)基於效率和類型檢查的考慮,應該儘量使用array,沒法肯定數組大小時才使用ArrayList

 

(14)儘可能使用HashMap、ArrayList、StringBuilder,除非線程安全須要,不然不推薦使用Hashtable、Vector、StringBuffer,後三者因爲使用同步機制而致使了性能開銷

 

(15)不要將數組聲明爲public static final

 

由於這毫無心義,這樣只是定義了引用爲static final,數組的內容仍是能夠隨意改變的,將數組聲明爲public更是一個安全漏洞,這意味着這個數組能夠被外部類所改變

 

(16)儘可能在合適的場合使用單例

 

使用單例能夠減輕加載的負擔、縮短加載的時間、提升加載的效率,但並非全部地方都適用於單例,簡單來講,單例主要適用於如下三個方面:

  • 控制資源的使用,經過線程同步來控制資源的併發訪問

  • 控制實例的產生,以達到節約資源的目的

  • 控制數據的共享,在不創建直接關聯的條件下,讓多個不相關的進程或線程之間實現通訊

     

(17)儘可能避免隨意使用靜態變量

 

要知道,當某個對象被定義爲static的變量所引用,那麼gc一般是不會回收這個對象所佔有的堆內存的,如:

 

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

 

此時靜態變量b的生命週期與A類相同,若是A類不被卸載,那麼引用B指向的B對象會常駐內存,直到程序終止

 

(18)及時清除再也不須要的會話

 

爲了清除再也不活動的會話,許多應用服務器都有默認的會話超時時間,通常爲30分鐘。當應用服務器須要保存更多的會話時,若是內存不足,那麼操做系統會把部分數據轉移到磁盤,應用服務器也可能根據MRU(最近最頻繁使用)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出內存不足的異常。若是會話要被轉儲到磁盤,那麼必需要先被序列化,在大規模集羣中,對對象進行序列化的代價是很昂貴的。所以,當會話再也不須要時,應當及時調用HttpSession的invalidate()方法清除會話。

 

 

(19)實現RandomAccess接口的集合好比ArrayList,應當使用最普通的for循環而不是foreach循環來遍歷

 

這是JDK推薦給用戶的。JDK API對於RandomAccess接口的解釋是:實現RandomAccess接口用來代表其支持快速隨機訪問,此接口的主要目的是容許通常的算法更改其行爲,從而將其應用到隨機或連續訪問列表時能提供良好的性能。實際經驗代表,實現RandomAccess接口的類實例,假如是隨機訪問的,使用普通for循環效率將高於使用foreach循環;反過來,若是是順序訪問的,則使用Iterator會效率更高。可使用相似以下的代碼做判斷:

 

if (list instanceof RandomAccess)
{
  for (int i = 0; i < list.size(); i++){}
}
else
{
  Iterator<?> iterator = list.iterable();
  while (iterator.hasNext()){iterator.next()}
}

 

foreach循環的底層實現原理就是迭代器Iterator,參見Java語法糖1:可變長度參數以及foreach循環原理。因此後半句」反過來,若是是順序訪問的,則使用Iterator會效率更高」的意思就是順序訪問的那些類實例,使用foreach循環去遍歷。

 

(20)使用同步代碼塊替代同步方法

 

這點在多線程模塊中的synchronized鎖方法塊一文中已經講得很清楚了,除非能肯定一整個方法都是須要進行同步的,不然儘可能使用同步代碼塊,避免對那些不須要進行同步的代碼也進行了同步,影響了代碼執行效率。

 

(21)將常量聲明爲static final,並以大寫命名

 

這樣在編譯期間就能夠把這些內容放入常量池中,避免運行期間計算生成常量的值。另外,將常量的名字以大寫命名也能夠方便區分出常量與變量

 

(22)不要建立一些不使用的對象,不要導入一些不使用的類

 

這毫無心義,若是代碼中出現」The value of the local variable i is not used」、」The import java.util is never used」,那麼請刪除這些無用的內容

 

(23)程序運行過程當中避免使用反射

 

反射是Java提供給用戶一個很強大的功能,功能強大每每意味着效率不高。不建議在程序運行過程當中使用尤爲是頻繁使用反射機制,特別是Method的invoke方法,若是確實有必要,一種建議性的作法是將那些須要經過反射加載的類在項目啓動的時候經過反射實例化出一個對象並放入內存—-用戶只關心和對端交互的時候獲取最快的響應速度,並不關心對端的項目啓動花多久時間。

 

(24)使用數據庫鏈接池和線程池

 

這兩個池都是用於重用對象的,前者能夠避免頻繁地打開和關閉鏈接,後者能夠避免頻繁地建立和銷燬線程。

相關文章
相關標籤/搜索