天天看點

Java 性能優化--初級優化

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、 盡量減少對變量的重複計算 

例如:for(int i = 0;i < list.size; i ++) { 

… 

應替換為: 

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

10、盡量采用lazy loading 的政策,即在需要的時候才開始建立。 

例如: String str = “aaa”; 

if(i == 1) { 

list.add(str); 

String str = “aaa”; 

11、慎用異常 

異常對性能不利。抛出異常首先要建立一個新的對象。Throwable接口的構造函數調用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤資訊。隻要有異常被抛出,VM就必須調整調用堆棧,因為在處理過程中建立了一個新的對象。 異常隻能用于錯誤處理,不應該用來控制程式流程。 

12、不要在循環中使用: 

Try { 

} catch() { 

應把其放置在最外層。 

13、StringBuffer 的使用: 

StringBuffer表示了可變的、可寫的字元串。 

有三個構造方法 : 

StringBuffer (); //預設配置設定16個字元的空間 

StringBuffer (int size); //配置設定size個字元的空間 

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加入元素的例子: 

Object obj = new Object(); 

Vector v = new Vector(100000); 

for(int I=0; 

I<100000; I++) { v.add(0,obj); } 

  除非有絕對充足的理由要求每次都把新元素插入到Vector的前面,否則上面的代碼對性能不利。在預設構造函數中,Vector的初始存儲能力是10個元素,如果新元素加入時存儲能力不足,則以後存儲能力每次加倍。Vector類就象StringBuffer類一樣,每次擴充存儲能力時,所有現有的元素都要複制到新的存儲空間之中。下面的代碼片段要比前面的例子快幾個數量級: 

for(int I=0; I<100000; I++) { v.add(obj); } 

  同樣的規則也适用于Vector類的remove()方法。由于Vector中各個元素之間不能含有“空隙”,删除除最後一個元素之外的任意其他元素都導緻被删除元素之後的元素向前移動。也就是說,從Vector删除最後一個元素要比删除第一個元素“開銷”低好幾倍。 

  假設要從前面的Vector删除所有元素,我們可以使用這種代碼: 

for(int I=0; I<100000; I++) 

 v.remove(0); 

  但是,與下面的代碼相比,前面的代碼要慢幾個數量級: 

 v.remove(v.size()-1); 

  從Vector類型的對象v删除所有元素的最好方法是: 

v.removeAllElements(); 

  假設Vector類型的對象v包含字元串“Hello”。考慮下面的代碼,它要從這個Vector中删除“Hello”字元串: 

String s = "Hello"; 

int i = v.indexOf(s); 

if(I != -1) v.remove(s); 

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

if(I != -1) v.remove(i); 

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

String s = "Hello"; v.remove(s); 

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

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、代碼重構:增強代碼的可讀性。 

例如: 

public class ShopCart { 

private List carts ; 

public void add (Object item) { 

if(carts == null) { 

carts = new ArrayList(); 

crts.add(item); 

public void remove(Object item) { 

if(carts. contains(item)) { 

carts.remove(item); 

public List getCarts() { 

//傳回隻讀清單 

return Collections.unmodifiableList(carts); 

//不推薦這種方式 

//this.getCarts().add(item); 

17、不用new關鍵詞建立類的執行個體 

用new關鍵詞建立類的執行個體時,構造函數鍊中的所有構造函數都會被自動調用。但如果一個對象實作了Cloneable接口,我們可以調用它的clone()方法。clone()方法不會調用任何類構造函數。 

在使用設計模式(Design Pattern)的場合,如果用Factory模式建立對象,則改用clone()方法建立新的對象執行個體非常簡單。例如,下面是Factory模式的一個典型實作: 

public static Credit getNewCredit() { 

return new Credit(); 

改進後的代碼使用clone()方法,如下所示: 

private static Credit BaseCredit = new Credit(); 

return (Credit) BaseCredit.clone(); 

上面的思路對于數組處理同樣很有用。 

18、乘法和除法 

考慮下面的代碼: 

for (val = 0; val < 100000; val +=5) { 

alterX = val * 8; myResult = val * 2; 

用移位操作替代乘法操作可以極大地提高性能。下面是修改後的代碼: 

for (val = 0; val < 100000; val += 5) { 

alterX = val << 3; myResult = val << 1; 

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

25、不要将數組聲明為:public static final 。 會把這個array數組定死了,排序啊,增删改都沒發進行了。

26、HashMap的周遊效率讨論 

經常遇到對HashMap中的key和value值對的周遊操作,有如下兩種方法:Map<String, String[]> paraMap = new HashMap<String, String[]>(); 

................//第一個循環 

Set<String> appFieldDefIds = paraMap.keySet(); 

for (String appFieldDefId : appFieldDefIds) { 

String[] values = paraMap.get(appFieldDefId); 

...... 

//第二個循環 

for(Entry<String, String[]> entry : paraMap.entrySet()){ 

String appFieldDefId = entry.getKey(); 

String[] values = entry.getValue(); 

....... 

第一種實作明顯的效率不如第二種實作。 

分析如下 Set<String> appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet 

代碼如下: 

public Set<K> keySet() { 

Set<K> ks = keySet; 

return (ks != null ? ks : (keySet = new KeySet())); 

private class KeySet extends AbstractSet<K> { 

public Iterator<K> iterator() { 

return newKeyIterator(); 

public int size() { 

return size; 

public boolean contains(Object o) { 

return containsKey(o); 

public boolean remove(Object o) { 

return HashMap.this.removeEntryForKey(o) != null; 

public void clear() { 

HashMap.this.clear(); 

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

再來看看for/in循環的文法 

for(declaration : expression) 

statement 

在執行階段被翻譯成如下各式 

for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){ 

declaration = #i.next(); 

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

Iterator<K> newKeyIterator() { 

return new KeyIterator(); 

private class KeyIterator extends HashIterator<K> { 

public K next() { 

return nextEntry().getKey(); 

是以在for中還是調用了 

在第二個循環for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是如下的一個内部類 

private class EntryIterator extends HashIterator<Map.Entry<K,V>> { 

public Map.Entry<K,V> next() { 

return nextEntry(); 

此時第一個循環得到key,第二個循環得到HashMap的Entry 

效率就是從循環裡面展現出來的第二個循環此緻可以直接取key和value值 

而第一個循環還是得再利用HashMap的get(Object key)來取value值 

現在看看HashMap的get(Object key)方法 

public V get(Object key) { 

Object k = maskNull(key); 

int hash = hash(k); 

int i = indexFor(hash, table.length); //Entry[] table 

Entry<K,V> e = table; 

while (true) { 

if (e == null) 

return null; 

if (e.hash == hash && eq(k, e.key)) 

return e.value; 

e = e.next; 

其實就是再次利用Hash值取出相應的Entry做比較得到結果,是以使用第一中循環相當于兩次進入HashMap的Entry中 

而第二個循環取得Entry的值之後直接取key和value,效率比第一個循環高。其實按照Map的概念來看也應該是用第二個循環好一點,它本來就是key和value的值對,将key和value分開操作在這裡不是個好選擇。 

27、array(數組) 和 ArryList的使用 

array([]):最高效;但是其容量固定且無法動态改變; 

ArrayList:容量可動态增長;但犧牲效率; 

基于效率和類型檢驗,應盡可能使用array,無法确定數組大小時才使用ArrayList! 

ArrayList是Array的複雜版本 

ArrayList内部封裝了一個Object類型的數組,從一般的意義來說,它和數組沒有本質的差别,甚至于ArrayList的許多方法,如Index、IndexOf、Contains、Sort等都是在内部數組的基礎上直接調用Array的對應方法。 

ArrayList存入對象時,抛棄類型資訊,所有對象屏蔽為Object,編譯時不檢查類型,但是運作時會報錯。 

注:jdk5中加入了對泛型的支援,已經可以在使用ArrayList時進行類型檢查。 

從這一點上看來,ArrayList與數組的差別主要就是由于動态增容的效率問題了 

28、盡量使用HashMap 和ArrayList ,除非必要,否則不推薦使用HashTable和Vector ,後者由于使用同步機制,而導緻了性能的開銷。 

29、StringBuffer 和StringBuilder的差別: 

java.lang.StringBuffer線程安全的可變字元序列。一個類似于 String 的字元串緩沖區,但不能修改。StringBuilder。與該類相比,通常應該優先使用 java.lang.StringBuilder類,因為它支援所有相同的操作,但由于它不執行同步,是以速度更快。為了獲得更好的性能,在構造 StirngBuffer 或 StirngBuilder 時應盡可能指定它的容量。當然,如果你操作的字元串長度不超過 16 個字元就不用了。 相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%-15% 左右的性能提升,但卻要冒多線程不安全的風險。而在現實的子產品化程式設計中,負責某一子產品的程式員不一定能清晰地判斷該子產品是否會放入多線程的環境中運作,是以:除非你能确定你的系統的瓶頸是在 StringBuffer 上,并且确定你的子產品不會運作在多線程模式下,否則還是用 StringBuffer 吧。

繼續閱讀