程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> java編程中的性能提升問題

java編程中的性能提升問題

編輯:JAVA綜合教程

java編程中的性能提升問題


軟件產品猶如一棟大樓,大樓在建設初期,會有樓房規劃,建築構想,打牢地基,後面才是施工人員進行進行實質性的建設。要保證軟件產品的高質量,優秀的架構,優秀的產品設計,是產生高質量的前提。同時,沒有過硬的編碼實現,一樣得不到預期的效果。縱觀現在的產品,產品架構沒多大差別,基本運用基線版本進行局點定制。而系統中的一些功能性能常常不過關,問題往往就出在編碼實現上。這塊是開發人員在開發過程中需要注意的。在JAVA程序中,性能問題的大部分原因並不在於JAVA語言,而是程序本身。養成良好的編碼習慣非常重要,能夠顯著地提升程序性能。下面針對幾個要點來講解下java編程中的性能提升。

1. 盡量在合適的場合使用單例

使用單例可以減輕加載的負擔,縮短加載的時間,提高加載的效率,但並不是所有地方都適用於單例,簡單來說,單例主要適用於以下三個方面:

第一,控制資源的使用,通過線程同步來控制資源的並發訪問;

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

第三,控制數據共享,在不建立直接關聯的條件下,讓多個不相關的進程或線程之間實現通信。

2. 盡量避免隨意使用靜態變量

要知道,當某個對象被定義為static變量所引用,那麼GC通常是不會回收這個對象所占有的內存,如

1 2 3 publicclass A{ privatestatic B b = newB(); }

此時靜態變量b的生命周期與A類同步,如果A類不會卸載,那麼b對象會常駐內存,直到程序終止。

3. 盡量避免過多過常的創建Java對象

盡量避免在經常調用的方法,循環中new對象,由於系統不僅要花費時間來創建對象,而且還要花時間對這些對象進行垃圾回收和處理,在我們可以控制的范圍內,最大限度的重用對象,最好能用基本的數據類型或數組來替代對象。

4. 盡量使用final修飾符

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

如:讓訪問實例內變量的getter/setter方法變成”final:

簡單的getter/setter方法應該被置成final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”,例子:

 

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 classMAF { publicvoid setSize (intsize) { _size = size; } privateint _size; } 更正 classDAF_fixed { finalpublic void setSize (intsize) { _size = size; } privateint _size; }

 

5. 盡量使用局部變量

調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態變量,實例變量等,都在堆(Heap)中創建,速度較慢。

6. 盡量處理好包裝類型和基本類型兩者的使用場所

雖然包裝類型和基本類型在使用過程中是可以相互轉換,但它們兩者所產生的內存區域是完全不同的,基本類型數據產生和處理都在棧中處理,包裝類型是對象,是在堆中產生實例。在集合類對象,有對象方面需要的處理適用包裝類型,其他的處理提倡使用基本類型。

7. 慎用synchronized,盡量減小synchronize的方法

都知道,實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。synchronize方法被調用時,直接會把當前對象鎖 了,在方法執行完之前其他線程無法調用當前對象的其他方法。所以synchronize的方法盡量小,並且應盡量使用方法同步代替代碼塊同步。

9. 盡量不要使用finalize方法

實際上,將資源清理放在finalize方法中完成是非常不好的選擇,由於GC的工作量很大,尤其是回收Young代內存時,大都會引起應用程序暫停,所以再選擇使用finalize方法進行資源清理,會導致GC負擔更大,程序運行效率更差。

10. 盡量使用基本數據類型代替對象

String str = "hello";

上面這種方式會創建一個“hello”字符串,而且JVM的字符緩存池還會緩存這個字符串;

String str = new String("hello");

此時程序除創建字符串外,str所引用的String對象底層還包含一個char[]數組,這個char[]數組依次存放了h,e,l,l,o

11. 多線程在未發生線程安全前提下應盡量使用HashMap、ArrayList

HashTable、Vector等使用了同步機制,降低了性能。

12. 盡量合理的創建HashMap

當你要創建一個比較大的hashMap時,充分利用這個構造函數

 

1 publicHashMap(intinitialCapacity, floatloadFactor);

 

避免HashMap多次進行了hash重構,擴容是一件很耗費性能的事,在默認中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能准確的估計你所需要的最佳大小,同樣的Hashtable,Vectors也是一樣的道理。

13. 盡量減少對變量的重復計算

如:

 

1 for(inti=0;i

 

應該改為

 

1 for(inti=0,len=list.size();i

 

並且在循環中應該避免使用復雜的表達式,在循環中,循環條件會被反復計算,如果不使用復雜表達式,而使循環條件值不變的話,程序將會運行的更快。

14. 盡量避免不必要的創建

如:

 

1 2 3 A a = newA(); if(i==1){list.add(a);}

 

應該改為

 

1 2 3 4 5 6 7 if(i==1){ A a = newA(); list.add(a); }

 

 

 

15. 盡量在finally塊中釋放資源

程序中使用到的資源應當被釋放,以避免資源洩漏。這最好在finally塊中去做。不管程序執行的結果如何,finally塊總是會執行的,以確保資源的正確關閉。

16. 盡量使用移位來代替'a/b'的操作

"/"是一個代價很高的操作,使用移位的操作將會更快和更有效

 

1 2 3 intnum = a / 4; intnum = a / 8;

 

應該改為

 

1 2 3 intnum = a >> 2; intnum = a >> 3;

 

但注意的是使用移位應添加注釋,因為移位操作不直觀,比較難理解

17.盡量使用移位來代替'a*b'的操作

同樣的,對於'*'操作,使用移位的操作將會更快和更有效

 

1 2 3 intnum = a * 4; intnum = a * 8;

 

應該改為

 

1 2 3 intnum = a << 2; intnum = a << 3;

 

18. 盡量確定StringBuffer的容量

StringBuffer 的構造器會創建一個默認大小(通常是16)的字符數組。在使用中,如果超出這個大小,就會重新分配內存,創建一個更大的數組,並將原先的數組復制過來,再 丟棄舊的數組。在大多數情況下,你可以在創建 StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。

如:

1 StringBuffer buffer = newStringBuffer(1000);

 

19. 盡量早釋放無用對象的引用

大部分時,方法局部引用變量所引用的對象 會隨著方法結束而變成垃圾,因此,大部分時候程序無需將局部,引用變量顯式設為null。

例如:

Java代碼

 

1 2 3 4 5 6 7 8 9 Publicvoidtest(){ Object obj = newObject(); …… Obj=null; }

 

上面這個就沒必要了,隨著方法test()的執行完成,程序中obj引用變量的作用域就結束了。但是如果是改成下面:

Java代碼

 

1 2 3 4 5 6 7 8 9 10 11 12 13 Publicvoidtest(){ Object obj = newObject(); …… Obj=null; //執行耗時,耗內存操作;或調用耗時,耗內存的方法 …… }

 

這時候就有必要將obj賦值為null,可以盡早的釋放對Object對象的引用。

20. 盡量避免使用二維數組

二維數據占用的內存空間比一維數組多得多,大概10倍以上。

21. 盡量避免使用split

除非是必須的,否則應該避免使用split,split由於支持正則表達式,所以效率比較低,如果是頻繁的幾十,幾百萬的調用將會耗費大量資源,如果確實需要頻繁的調用split,可以考慮使用apache的StringUtils.split(string,char),頻繁split的可以緩存結果。

22. ArrayList & LinkedList

一個是線性表,一個是鏈表,一句話,隨機查詢盡量使用ArrayList,ArrayList優於LinkedList,LinkedList還要移動指針,添加刪除的操作LinkedList優於ArrayList,ArrayList還要移動數據,不過這是理論性分析,事實未必如此,重要的是理解好2者得數據結構,對症下藥。

23. 盡量使用System.arraycopy ()代替通過來循環復制數組

System.arraycopy() 要比通過循環來復制數組快的多

24. 盡量緩存經常使用的對象

盡可能將經常使用的對象進行緩存,可以使用數組,或HashMap的容器來進行緩存,但這種方式可能導致系統占用過多的緩存,性能下降,推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行緩存,他們基本都實現了FIFO/FLU等緩存算法。

25. 盡量避免非常大的內存分配

有時候問題不是由當時的堆狀態造成的,而是因為分配失敗造成的。分配的內存塊都必須是連續的,而隨著堆越來越滿,找到較大的連續塊越來越困難。

26. 慎用異常

當創建一個異常時,需要收集一個棧跟蹤(stack track),這個棧跟蹤用於描述異常是在何處創建的。構建這些棧跟蹤時需要為運行時棧做一份快照,正是這一部分開銷很大。當需要創建一個 Exception 時,JVM 不得不說:先別動,我想就您現在的樣子存一份快照,所以暫時停止入棧和出棧操作。棧跟蹤不只包含運行時棧中的一兩個元素,而是包含這個棧中的每一個元素。

如果您創建一個 Exception ,就得付出代價。好在捕獲異常開銷不大,因此可以使用 try-catch 將核心內容包起來。從技術上講,您甚至可以隨意地拋出異常,而不用花費很大的代價。招致性能損失的並不是 throw 操作——盡管在沒有預先創建異常的情況下就拋出異常是有點不尋常。真正要花代價的是創建異常。幸運的是,好的編程習慣已教會我們,不應該不管三七二十一就拋出異常。異常是為異常的情況而設計的,使用時也應該牢記這一原則。

27. 盡量重用對象

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

28. 不要重復初始化變量

默認情況下,調用類的構造函數時,java會把變量初始化成確定的值,所有的對象被設置成null,整數變量設置成0,float和double變量設置成0.0,邏輯值設置成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵字創建一個對象時,構造函數鏈中的所有構造函數都會被自動調用。
這裡有個注意,給成員變量設置初始值但需要調用其他方法的時候,最好放在一個方法比如initXXX()中,因為直接調用某方法賦值可能會因為類尚未初始化而拋空指針異常,如:public int state = this.getState();

29. 在java+Oracle的應用系統開發中,java中內嵌的SQL語言應盡量使用大寫形式,以減少Oracle解析器的解析負擔。

30. 在java編程過程中,進行數據庫連接,I/O流操作,在使用完畢後,及時關閉以釋放資源。因為對這些大對象的操作會造成系統大的開銷。

31. 過分的創建對象會消耗系統的大量內存,嚴重時,會導致內存洩漏,因此,保證過期的對象的及時回收具有重要意義。JVM的GC並非十分智能,因此建議在對象使用完畢後,手動設置成null。

32. 在使用同步機制時,應盡量使用方法同步代替代碼塊同步。

33. 不要在循環中使用Try/Catch語句,應把Try/Catch放在循環最外層

Error是獲取系統錯誤的類,或者說是虛擬機錯誤的類。不是所有的錯誤Exception都能獲取到的,虛擬機報錯Exception就獲取不到,必須用Error獲取。

34. 通過StringBuffer的構造函數來設定他的初始化容量,可以明顯提升性能

StringBuffer的默認容量為16,當StringBuffer的容量達到最大容量時,她會將自身容量增加到當前的2倍+2,也就是2*n+2。無論何時,只要StringBuffer到達她的最大容量,她就不得不創建一個新的對象數組,然後復制舊的對象數組,這會浪費很多時間。所以給StringBuffer設置一個合理的初始化容量值,是很有必要的!

35. 合理使用java.util.Vector

Vector與StringBuffer類似,每次擴展容量時,所有現有元素都要賦值到新的存儲空間中。Vector的默認存儲能力為10個元素,擴容加倍。
vector.add(index,obj) 這個方法可以將元素obj插入到index位置,但index以及之後的元素依次都要向下移動一個位置(將其索引加 1)。 除非必要,否則對性能不利。同樣規則適用於remove(int index)方法,移除此向量中指定位置的元素。將所有後續元素左移(將其索引減 1)。返回此向量中移除的元素。所以刪除vector最後一個元素要比刪除第1個元素開銷低很多。刪除所有元素最好用removeAllElements()方法。
如果要刪除vector裡的一個元素可以使用 vector.remove(obj);而不必自己檢索元素位置,再刪除,如int index = indexOf(obj);vector.remove(index);

38. 不用new關鍵字創建對象的實例
用new關鍵詞創建類的實例時,構造函數鏈中的所有構造函數都會被自動調用。但如果一個對象實現了Cloneable接口,我們可以調用她的clone()方法。clone()方法不會調用任何類構造函數。
下面是Factory模式的一個典型實現:

 

? 1 2 3 4 publicstatic Credit getNewCredit() { returnnew Credit(); }

 

改進後的代碼使用clone()方法:

 

? 1 2 3 4 5 privatestatic Credit BaseCredit = newCredit(); publicstatic Credit getNewCredit() { return(Credit)BaseCredit.clone(); }

 

39. 不要將數組聲明為:public static final
40. HaspMap的遍歷:

 

? 1 2 3 4 5 6 Map paraMap = newHashMap(); for( Entry entry : paraMap.entrySet() ) { String appFieldDefId = entry.getKey(); String[] values = entry.getValue(); }

 

利用散列值取出相應的Entry做比較得到結果,取得entry的值之後直接取key和value。

41. array(數組)和ArrayList的使用
array 數組效率最高,但容量固定,無法動態改變,ArrayList容量可以動態增長,但犧牲了效率。

42. 單線程應盡量使用 HashMap, ArrayList,除非必要,否則不推薦使用HashTable,Vector,她們使用了同步機制,而降低了性能。

43. StringBuffer,StringBuilder的區別在於:java.lang.StringBuffer 線程安全的可變字符序列。一個類似於String的字符串緩沖區,但不能修改。StringBuilder與該類相比,通常應該優先使用StringBuilder類,因為她支持所有相同的操作,但由於她不執行同步,所以速度更快。為了獲得更好的性能,在構造StringBuffer或StringBuilder時應盡量指定她的容量。當然如果不超過16個字符時就不用了。 相同情況下,使用StringBuilder比使用StringBuffer僅能獲得10%~15%的性能提升,但卻要冒多線程不安全的風險。綜合考慮還是建議使用StringBuffer。

44. 盡量使用基本數據類型代替對象。

45. 使用具體類比使用接口效率高,但結構彈性降低了,但現代IDE都可以解決這個問題。

46. 考慮使用靜態方法,如果你沒有必要去訪問對象的外部,那麼就使你的方法成為靜態方法。她會被更快地調用,因為她不需要一個虛擬函數導向表。這同事也是一個很好的實踐,因為她告訴你如何區分方法的性質,調用這個方法不會改變對象的狀態。

47. 應盡可能避免使用內在的GET,SET方法。

48.避免枚舉,浮點數的使用。

 

49、字符串操作 (A)StringBuffer 在線程安全時(比如涉及到多線程時),用此方式進行字符串的拼接。 (B)StringBuilder 在非線程安全時,用此方式來拼接 (C)“+”號進行靜態字符串的拼接   RePlaceAll()方法的使用: java.lang.String.replaceAll()方法給定的替換此字符串匹配給定的正則表達式替換每個子 聲明

以下是java.lang.String.replaceAll()方法的聲明

public String replaceAll(String regex, String replacement)參數

regex-- 這是此字符串是要匹配的正則表達式.

replacement--這是每個匹配項的字符串來代替.

返回值

此方法返回的結果字符串.

異常

  • PatternSyntaxException-- 如果正則表達式的語法無效.

     

    實例

     

    下面的示例演示使用的java.lang.String.replaceAll()方法.

    package com.yiibai;
    
    import java.lang.*;
    
    public class StringDemo {
    
      public static void main(String[] args) {
      
        String str1 = "!!Tutorials!!Point", str2;
        String substr = "**", regex = "!!";
        
        // prints string1
        System.out.println("String = " + str1);
        
        /* replaces each substring of this string that matches the given
        regular expression with the given replacement */
        str2 = str1.replaceAll(regex, substr);    
        System.out.println("After Replacing = " + str2);
      }
    }

50、對於定義常量字符串,不要用new的方式來創建常量字符串,可以使用賦值方式為變量設置。 String str="abc"; * 引用數據類型肯定存放在堆中 棧中放置的是參數變量而不能放對象 對象只能放在堆中
*******************
它只創建一個對象 在堆中創建一個對String類的對象引用變量str(引用變量肯定是存放在堆裡的),然後查找棧中是否有"abc",若沒有則將"abc"存放進棧,並令str指向"abc",若已經存在則直接令str指向"abc".(也就是說引用變量本身只能存放在堆中 它的值是所指向的字符串abc 它的地址存放在棧中) 它創建多個"abc"字符串在內存中其實只存在一個對象而已,這樣有利於節省內存空間同時在一定程度上提高程序運行速度
******************************
String str=new String("abc");* 所以通過new操作符的操作都是在堆完成的
******************************
它創建兩個對象 abc對象和str引用對象 兩個必須存放在堆中 str指向堆中的abc對象 也就是說 兩個對象和str的地址全部存放在堆中 因為使用了new操作符 所以下面的例子裡str2,str3和str4即使是值都為abc因為str2的地址在棧中 str3和str4的地址各自開辟空間 所以他們的地址肯定不一樣了
但是它們的值是一樣的 那就是abc 51、子串匹配查找 可以使用String.indexOf(String str)
int indexOf(int ch)   返回指定字符在此字符串中第一次出現處的索引。 
int indexOf(int ch, int fromIndex) 從指定的索引開始搜索,返回在此字符串中第一次出現指定字符處的索引。 
int indexOf(String str) 返回第一次出現的指定子字符串在此字符串中的索引。 
int indexOf(String str, int fromIndex) 從指定的索引處開始,返回第一次出現的指定子字符串在此字符串中的索引。
matchs(String regex)方法性能最差,查看源碼得知在每一次調用時,都要創建新的Pattern和matchs對象並重新編譯正則表達式。

 

以下舉幾個實用優化的例子:

一、避免在循環條件中使用復雜表達式

在不做編譯優化的情況下,在循環中,循環條件會被反復計算,如果不使用復雜表達式,而使循環條件值不變的話,程序將會運行的更快。例子:

 

? 1 2 3 4 5 6 7 importjava.util.Vector; classCEL { voidmethod (Vector vector) { for(inti = 0; i < vector.size (); i++) // Violation ;// ... } }

 

更正:

 

? 1 2 3 4 5 6 7 classCEL_fixed { voidmethod (Vector vector) { intsize = vector.size () for(inti = 0; i < size; i++) ;// ... } }

 

二、為'Vectors' 和 'Hashtables'定義初始大小
JVM為Vector擴充大小的時候需要重新創建一個更大的數組,將原原先數組中的內容復制過來,最後,原先的數組再被回收。可見Vector容量的擴大是一個頗費時間的事。

通常,默認的10個元素大小是不夠的。你最好能准確的估計你所需要的最佳大小。例子:

import java.util.Vector;
public class DIC {
public void addObjects (Object[] o) {
// if length > 10, Vector needs to expand
for (int i = 0; i< o.length;i++) {
v.add(o); // capacity before it can add more elements.
}
}
public Vector v = new Vector(); // no initialCapacity.
}

更正:

自己設定初始大小。

public Vector v = new Vector(20);
public Hashtable hash = new Hashtable(10);

三、在finally塊中關閉Stream

程序中使用到的資源應當被釋放,以避免資源洩漏。這最好在finally塊中去做。不管程序執行的結果如何,finally塊總是會執行的,以確保資源的正確關閉。

四、使用'System.arraycopy ()'代替通過來循環復制數組,例子:

public class IRB
{
void method () {
int[] array1 = new int [100];
for (int i = 0; i < array1.length; i++) {
array1 [i] = i;
}
int[] array2 = new int [100];
for (int i = 0; i < array2.length; i++) {
array2 [i] = array1 [i]; // Violation
}
}
}

 

 

 

更正:

public class IRB
{
void method () {
int[] array1 = new int [100];
for (int i = 0; i < array1.length; i++) {
array1 [i] = i;
}
int[] array2 = new int [100];
System.arraycopy(array1, 0, array2, 0, 100);
}
}

五、讓訪問實例內變量的getter/setter方法變成”final”

簡單的getter/setter方法應該被置成final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”,例子:

class MAF {
public void setSize (int size) {
_size = size;
}
private int _size;
}

更正:

class DAF_fixed {
final public void setSize (int size) {
_size = size;
}
private int _size;
}

六、對於常量字符串,用'String' 代替 'StringBuffer'
常量字符串並不需要動態改變長度。

例子:

public class USC {
String method () {
StringBuffer s = new StringBuffer ("Hello");
String t = s + "World!";
return t;
}
}

更正:把StringBuffer換成String,如果確定這個String不會再變的話,這將會減少運行開銷提高性能。

七、在字符串相加的時候,使用 ' ' 代替 " ",如果該字符串只有一個字符的話

例子:

public class STR {
public void method(String s) {
String string = s + "d" // violation.
string = "abc" + "d" // violation.
}
}

更正:

將一個字符的字符串替換成' '

public class STR {
public void method(String s) {
String string = s + 'd'
string = "abc" + 'd'
}
}

以上僅是Java方面編程時的性能優化,性能優化大部分都是在時間、效率、代碼結構層次等方面的權衡,各有利弊,不要把上面內容當成教條,或許有些對我們實際工作適用,有些不適用,還望根據實際工作場景進行取捨吧,活學活用,變通為宜。


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved