java代碼優化

可供程序利用的資源(內存、CPU時間、網絡帶寬等)是有限的,優化的目的就是讓程序用盡量少的資源完成預約的任務。優化一般包含兩方面的內容:減少代碼的體積,提升代碼的運行效率。本文討論的主要是如何提升代碼的效率html


在Java程序中,性能問題的大部分緣由並不在於Java語言,而是在於程序自己。養成好的代碼編寫習慣很是重要,好比正確地、巧妙地運用java.lang.String類和java.util.Vector類,它可以顯著地提升程序的性能。下面咱們就來具體地分析一下這方面的問題。

1.儘可能指定類的final修飾符 帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String。爲String類指定final防止了人們覆蓋length()方法。另外,若是指定一個類爲final,則該類全部的方法都是final。Java編譯器會尋找機會內聯(inline)全部的final方法(這和具體的編譯器實現有關)。此舉可以使性能平均提升50% 。

2.儘可能重用對象。特別是String 對象的使用中,出現字符串鏈接狀況時應用StringBuffer 代替。因爲系統不只要花時間生成對象,之後可能還需花時間對這些對象進行垃圾回收和處理。所以,生成過多的對象將會給程序的性能帶來很大的影響。

3.儘可能使用局部變量,調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧(Stack)中,速度較快。其餘變量,如靜態變量、實例變量等,都在堆(Heap)中建立,速度較慢。另外,依賴於具體的編譯器/JVM,局部變量還可能獲得進一步優化。請參見《儘量使用堆棧變量》。

4.不要重複初始化變量  默認狀況下,調用類的構造函數時, Java會把變量初始化成肯定的值:全部的對象被設置成null,整數變量(byte、short、int、long)設置成0,float和double變量設置成0.0,邏輯值設置成false。當一個類從另外一個類派生時,這一點尤爲應該注意,由於用new關鍵詞建立一個對象時,構造函數鏈中的全部構造函數都會被自動調用。

5.在JAVA + ORACLE 的應用系統開發中,java中內嵌的SQL語句儘可能使用大寫的形式,以減輕ORACLE解析器的解析負擔。

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

7.因爲JVM的有其自身的GC機制,不須要程序開發者的過多考慮,從必定程度上減輕了開發者負擔,但同時也遺漏了隱患,過度的建立對象會消耗系統的大量內存,嚴重時會致使內存泄露,所以,保證過時對象的及時回收具備重要意義。JVM回收垃圾的條件是:對象不在被引用;然而,JVM的GC並不是十分的機智,即便對象知足了垃圾回收的條件也不必定會被當即回收。因此,建議咱們在對象使用完畢,應手動置成null。

8.在使用同步機制時,應儘可能使用方法同步代替代碼塊同步。

9.儘可能減小對變量的重複計算java

Java代碼   收藏代碼
  1. 例如:for(int i = 0;i < list.size; i ++) {  
  2.             …  
  3. }  
  4. 應替換爲:  
  5. for(int i = 0,int len = list.size();i < len; i ++) {  
  6.             …  
  7. }  

 

10.儘可能採用lazy loading 的策略,即在須要的時候纔開始建立。算法

Java代碼   收藏代碼
  1.     例如:    String str = 「aaa」;  
  2.             if(i == 1) {  
  3.                 list.add(str);  
  4. }  
  5. 應替換爲:  
  6.             if(i == 1) {  
  7. String str = 「aaa」;  
  8.                 list.add(str);  
  9. }  

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

12.不要在循環中使用:數據庫

Java代碼   收藏代碼
  1. Try {  
  2. catch() {  
  3. }  

 
應把其放置在最外層。

13.StringBuffer 的使用:
        StringBuffer表示了可變的、可寫的字符串。
有三個構造方法 :express

Java代碼   收藏代碼
  1. StringBuffer ();            //默認分配16個字符的空間  
  2. StringBuffer (int size);  //分配size個字符的空間  
  3. StringBuffer (String str);  //分配16個字符+str.length()個字符空間   

 
   你能夠經過StringBuffer的構造函數來設定它的初始化容量,這樣能夠明顯地提高性能。這裏提到的構造函數是StringBuffer(int length),length參數表示當前的StringBuffer能保持的字符數量。你也可使用ensureCapacity(int minimumcapacity)方法在StringBuffer對象建立以後設置它的容量。首先咱們看看StringBuffer的缺省行爲,然 後再找出一條更好的提高性能的途徑。
     StringBuffer在內部維護一個字符數組,當你使用缺省的構造函數來建立StringBuffer對象的時候,由於沒有設置初始化字符長度,StringBuffer的容量被初始化爲16個字符,也就是說缺省容量就是16個字符。當StringBuffer達到最大容量 的時候,它會將自身容量增長到當前的2倍再加2,也就是(2*舊值+2)。若是你使用缺省值,初始化以後接着往裏面追 加字符,在你追加到第16個字符的時候它會將容量增長到34(2*16+2),當追加到34個字符的時候就會將容量增長到 70(2*34+2)。不管何事只要StringBuffer到達它的最大容量它就不得不建立一個新的字符數組而後從新將舊字符和 新字符都拷貝一遍――這也太昂貴了點。因此老是給StringBuffer設置一個合理的初始化容量值是錯不了的,這樣會帶來 立竿見影的性能增益。
     StringBuffer初始化過程的調整的做用因而可知一斑。因此,使用一個合適的容量值來初始化StringBuffer永遠都是一個最佳的建議。

14.合理的使用Java類 java.util.Vector。
簡單地說,一個Vector就是一個java.lang.Object實例的數組。Vector與數組類似,它的元素能夠經過整數形式的索引訪問。可是,Vector類型的對象在建立以後,對象的大小可以根據元素的增長或者刪除而擴展、縮小。請考慮下面這個向Vector加入元素的例子:編程

Java代碼   收藏代碼
  1. Object obj = new Object();  
  2. Vector v = new Vector(100000);  
  3. for(int I=0;  
  4. I<100000; I++) { v.add(0,obj); }  

 

  除非有絕對充足的理由要求每次都把新元素插入到Vector的前面,不然上面的代碼對性能不利。在默認構造函數中,Vector的初始存儲能力是10個元素,若是新元素加入時存儲能力不足,則之後存儲能力每次加倍。Vector類就象StringBuffer類同樣,每次擴展存儲能力時,全部現有的元素都要複製到新的存儲空間之中。下面的代碼片斷要比前面的例子快幾個數量級:設計模式

Java代碼   收藏代碼
  1. Object obj = new Object();  
  2. Vector v = new Vector(100000);  
  3. for(int I=0; I<100000; I++) { v.add(obj); }  

 

  一樣的規則也適用於Vector類的remove()方法。因爲Vector中各個元素之間不能含有「空隙」,刪除除最後一個元素以外的任意其餘元素都致使被刪除元素以後的元素向前移動。也就是說,從Vector刪除最後一個元素要比刪除第一個元素「開銷」低好幾倍。

  假設要從前面的Vector刪除全部元素,咱們可使用這種代碼:數組

Java代碼   收藏代碼
  1. for(int I=0; I<100000; I++)  
  2. {  
  3.  v.remove(0);  
  4. }  

 

  可是,與下面的代碼相比,前面的代碼要慢幾個數量級:服務器

Java代碼   收藏代碼
  1. for(int I=0; I<100000; I++)  
  2. {  
  3.  v.remove(v.size()-1);  
  4. }  

 

  從Vector類型的對象v刪除全部元素的最好方法是:網絡

Java代碼   收藏代碼
  1. v.removeAllElements();  

 

  假設Vector類型的對象v包含字符串「Hello」。考慮下面的代碼,它要從這個Vector中刪除「Hello」字符串:

Java代碼   收藏代碼
  1. String s = "Hello";   
  2. int i = v.indexOf(s);   
  3. if(I != -1) v.remove(s);  

 
  這些代碼看起來沒什麼錯誤,但它一樣對性能不利。在這段代碼中,indexOf()方法對v進行順序搜索尋找字符串「Hello」,remove(s)方法也要進行一樣的順序搜索。改進以後的版本是:

Java代碼   收藏代碼
  1. String s = "Hello";  
  2. int i = v.indexOf(s);  
  3. if(I != -1) v.remove(i);  

 

  這個版本中咱們直接在remove()方法中給出待刪除元素的精確索引位置,從而避免了第二次搜索。一個更好的版本是:

Java代碼   收藏代碼
  1. String s = "Hello"; v.remove(s);  

 
  最後,咱們再來看一個有關Vector類的代碼片斷:

Java代碼   收藏代碼
  1. for(int I=0; I++;I < v.length)  

 
  若是v包含100,000個元素,這個代碼片斷將調用v.size()方法100,000次。雖然size方法是一個簡單的方法,但它仍舊須要一次方法調用的開銷,至少JVM須要爲它配置以及清除堆棧環境。在這裏,for循環內部的代碼不會以任何方式修改Vector類型對象v的大小,所以上面的代碼最好改寫成下面這種形式:
int size = v.size(); for(int I=0; I++;I<size)

  雖然這是一個簡單的改動,但它仍舊贏得了性能。畢竟,每個CPU週期都是寶貴的。

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

16.代碼重構:加強代碼的可讀性。
    例如:

Java代碼   收藏代碼
  1. public class ShopCart {  
  2.         private List carts ;  
  3.         …  
  4.         public void add (Object item) {  
  5.             if(carts == null) {  
  6.                 carts = new ArrayList();  
  7. }  
  8. crts.add(item);  
  9. }  
  10. public void remove(Object item) {  
  11.     if(carts. contains(item)) {  
  12.         carts.remove(item);  
  13. }  
  14. }  
  15. public List getCarts() {  
  16.     //返回只讀列表  
  17.     return Collections.unmodifiableList(carts);  
  18. }  
  19.   
  20. //不推薦這種方式  
  21. //this.getCarts().add(item);  
  22. }  

 
17.不用new關鍵詞建立類的實例 
用new關鍵詞建立類的實例時,構造函數鏈中的全部構造函數都會被自動調用。但若是一個對象實現了Cloneable接口,咱們能夠調用它的clone()方法。clone()方法不會調用任何類構造函數。 
在使用設計模式(Design Pattern)的場合,若是用Factory模式建立對象,則改用clone()方法建立新的對象實

例很是簡單。例如,

Java代碼   收藏代碼
  1. 下面是Factory模式的一個典型實現:   
  2. public static Credit getNewCredit() {  
  3.     return new Credit();  
  4. }   
  5. 改進後的代碼使用clone()方法,以下所示:  
  6. private static Credit BaseCredit = new Credit();  
  7. public static Credit getNewCredit() {  
  8.     return (Credit) BaseCredit.clone();  
  9. }   

 

  上面的思路對於數組處理一樣頗有用。

18.乘法和除法 

Java代碼   收藏代碼
  1. 考慮下面的代碼:   
  2. for (val = 0; val < 100000; val +=5) {  
  3. alterX = val * 8; myResult = val * 2;  
  4. }   
  5. 用移位操做替代乘法操做能夠極大地提升性能。下面是修改後的代碼:   
  6. for (val = 0; val < 100000; val += 5) {   
  7. alterX = val << 3; myResult = val << 1;  
  8. }  

 

修改後的代碼再也不作乘以8的操做,而是改用等價的左移3位操做,每左移1位至關於乘以2。相應地,右移1位操做至關於除以2。值得一提的是,雖然移位操做速度快,但可能使代碼比較難於理解,因此最好加上一些註釋。

19.在JSP頁面中關閉無用的會話。
    一個常見的誤解是覺得session在有客戶端訪問時就被建立,然而事實是直到某server端程序調用HttpServletRequest.getSession(true)這樣的語句時才被建立,注意若是JSP沒有顯示的使用 <%@page session="false"%> 關閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。因爲session會消耗內存資源,所以,若是不打算使用session,應該在全部的JSP中關閉它。
對於那些無需跟蹤會話狀態的頁面,關閉自動建立的會話能夠節省一些資源。使用以下page指令:<%@ page session="false"%>

20.JDBC與I/O 
若是應用程序須要訪問一個規模很大的數據集,則應當考慮使用塊提取方式。默認狀況下,JDBC每次提取32行數據。舉例來講,假設咱們要遍歷一個5000行的記錄集,JDBC必須調用數據庫157次才能提取到所有數據。若是把塊大小改爲512,則調用數據庫的次數將減小到10次。

 

21.Servlet與內存使用 
許多開發者隨意地把大量信息保存到用戶會話之中。一些時候,保存在會話中的對象沒有及時地被垃圾回收機制回收。從性能上看,典型的症狀是用戶感到系統週期性地變慢,卻又不能把緣由歸於任何一個具體的組件。若是監視JVM的堆空間,它的表現是內存佔用不正常地大起大落。
解決這類內存問題主要有二種辦法。第一種辦法是,在全部做用範圍爲會話的Bean中實現HttpSessionBindingListener接口。這樣,只要實現valueUnbound()方法,就能夠顯式地釋放Bean使用的資源。 另一種辦法就是儘快地把會話做廢。大多數應用服務器都有設置會話做廢間隔時間的選項。另外,也能夠用編程的方式調用會話的setMaxInactiveInterval()方法,該方法用來設定在做廢會話以前,Servlet容器容許的客戶請求的最大間隔時間,以秒計。

2二、使用緩衝標記 
一些應用服務器加入了面向JSP的緩衝標記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個功能,Open Symphony工程也一樣支持這個功能。JSP緩衝標記既可以緩衝頁面片段,也可以緩衝整個頁面。當JSP頁面執行時,若是目標片段已經在緩衝之中,則生成該片段的代碼就不用再執行。頁面級緩衝捕獲對指定URL的請求,並緩衝整個結果頁面。對於購物籃、目錄以及門戶網站的主頁來講,這個功能極其有用。對於這類應用,頁面級緩衝可以保存頁面執行的結果,供後繼請求使用。

2三、選擇合適的引用機制
在典型的JSP應用系統中,頁頭、頁腳部分每每被抽取出來,而後根據須要引入頁頭、頁腳。當前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動做。 
include指令:例如<%@ include file="copyright.html" %>。該指令在編譯時引入指定的資源。在編譯以前,帶有include指令的頁面和指定的資源被合併成一個文件。被引用的外部資源在編譯時就肯定,比運行時才肯定資源更高效。 
include動做:例如<jsp:include page="copyright.jsp" />。該動做引入指定頁面執行後生成的結果。因爲它在運行時完成,所以對輸出結果的控制更加靈活。但時,只有當被引用的內容頻繁地改變時,或者在對主頁面的請求沒有出現以前,被引用的頁面沒法肯定時,使用include動做才合算。

2四、及時清除再也不須要的會話 
爲了清除再也不活動的會話,許多應用服務器都有默認的會話超時時間,通常爲30分鐘。當應用服務器須要保存更多會話時,若是內存容量不足,操做系統會把部份內存數據轉移到磁盤,應用服務器也可能根據「最近最頻繁使用」(Most Recently Used)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出「內存不足」異常。在大規模系統中,串行化會話的代價是很昂貴的。當會話再也不須要時,應當及時調用HttpSession.invalidate()方法清除會話。HttpSession.invalidate()方法一般能夠在應用的退出頁面調用。

2五、不要將數組聲明爲:public static final 。

2六、HashMap的遍歷效率討論
常常遇到對HashMap中的key和value值對的遍歷操做,有以下兩種方法:

Java代碼   收藏代碼
  1. Map<String, String[]> paraMap = new HashMap<String, String[]>();  
  2. ................//第一個循環  
  3. Set<String> appFieldDefIds = paraMap.keySet();  
  4. for (String appFieldDefId : appFieldDefIds) {  
  5. String[] values = paraMap.get(appFieldDefId);  
  6. ......  
  7. }  
  8.   
  9. //第二個循環  
  10. for(Entry<String, String[]> entry : paraMap.entrySet()){  
  11. String appFieldDefId = entry.getKey();  
  12. String[] values = entry.getValue();  
  13. .......  
  14. }  

 

第一種實現明顯的效率不如第二種實現。
分析以下 Set<String> appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet

代碼以下:

Java代碼   收藏代碼
  1. public Set<K> keySet() {  
  2. Set<K> ks = keySet;  
  3. return (ks != null ? ks : (keySet = new KeySet()));  
  4. }  
  5.   
  6. private class KeySet extends AbstractSet<K> {  
  7. public Iterator<K> iterator() {  
  8. return newKeyIterator();  
  9. }  
  10. public int size() {  
  11. return size;  
  12. }  
  13. public boolean contains(Object o) {  
  14. return containsKey(o);  
  15. }  
  16. public boolean remove(Object o) {  
  17. return HashMap.this.removeEntryForKey(o) != null;  
  18. }  
  19. public void clear() {  
  20. HashMap.this.clear();  
  21. }  
  22. }  

 
其實就是返回一個私有類KeySet, 它是從AbstractSet繼承而來,實現了Set接口。

Java代碼   收藏代碼
  1. 再來看看for/in循環的語法  
  2. for(declaration : expression)  
  3. statement  
  4.   
  5. 在執行階段被翻譯成以下各式  
  6. for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){  
  7. declaration = #i.next();  
  8. statement  
  9. }  

 

所以在第一個for語句for (String appFieldDefId : appFieldDefIds) 中調用了HashMap.keySet().iterator() 而這個方法調用了newKeyIterator()

Java代碼   收藏代碼
  1. Iterator<K> newKeyIterator() {  
  2. return new KeyIterator();  
  3. }  
  4. private class KeyIterator extends HashIterator<K> {  
  5. public K next() {  
  6. return nextEntry().getKey();  
  7. }  
  8. }  

 

因此在for中仍是調用了
在第二個循環for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是以下的一個內部類

Java代碼   收藏代碼
  1. private class EntryIterator extends HashIterator<Map.Entry<K,V>> {  
  2. public Map.Entry<K,V> next() {  
  3. return nextEntry();  
  4. }  
  5. }  

 
此時第一個循環獲得key,第二個循環獲得HashMap的Entry
效率就是從循環裏面體現出來的第二個循環此致能夠直接取key和value值
而第一個循環仍是得再利用HashMap的get(Object key)來取value值

如今看看HashMap的get(Object key)方法

Java代碼   收藏代碼
  1. public V get(Object key) {  
  2. Object k = maskNull(key);  
  3. int hash = hash(k);  
  4. int i = indexFor(hash, table.length); //Entry[] table   
  5. Entry<K,V> e = table;  
  6. while (true) {  
  7. if (e == null)  
  8. return null;  
  9. if (e.hash == hash && eq(k, e.key))   
  10. return e.value;  
  11.         e = e.next;  
  12. }  
  13. }  

 

其實就是再次利用Hash值取出相應的Entry作比較獲得結果,因此使用第一中循環至關於兩次進入HashMap的Entry中而第二個循環取得Entry的值以後直接取key和value,效率比第一個循環高。其實按照Map的概念來看也應該是用第二個循環好一點,它原本就是key和value的值對,將key和value分開操做在這裏不是個好選擇。2七、array(數組) 和 ArryList的使用array([]):最高效;可是其容量固定且沒法動態改變;ArrayList:容量可動態增加;但犧牲效率;基於效率和類型檢驗,應儘量使用array,沒法肯定數組大小時才使用ArrayList!ArrayList是Array的複雜版本ArrayList內部封裝了一個Object類型的數組,從通常的意義來講,它和數組沒有本質的差異,甚至於ArrayList的許多方法,如Index、IndexOf、Contains、Sort等都是在內部數組的基礎上直接調用Array的對應方法。ArrayList存入對象時,拋棄類型信息,全部對象屏蔽爲Object,編譯時不檢查類型,可是運行時會報錯。注:jdk5中加入了對泛型的支持,已經能夠在使用ArrayList時進行類型檢查。從這一點上看來,ArrayList與數組的區別主要就是因爲動態增容的效率問題了2八、儘可能使用HashMap 和ArrayList ,除非必要,不然不推薦使用HashTable和Vector ,後者因爲使用同步機制,而致使了性能的開銷。

相關文章
相關標籤/搜索