讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 第10章 性能和效率 >

第10章 性能和效率

快,快點,再快點……大腦已經跟不上鼠標!

——佚名

在這個快餐時代,系統一直在提速,從未停步過,從每秒百萬條指令的CPU到現在的每秒萬億條指令的多核CPU,從最初發佈一個帖子需要等待N小時才有回復到現在的微博,一個消息在幾分鐘內就可以傳遍全球;從N天才能完成的一次轉賬交易,到現在的即時轉賬——我們進入了一個光速發展的時代,我們享受著,也在被追逐著——搾乾硬件資源,加速所有能加速的,提升所有能提升的。

建議132:提升Java性能的基本方法

Java從誕生之日起就被質疑:字節碼在JVM中運行是否會比機器碼直接運行的效率會低很多?很多技術高手、權威網站都有類似的測試和爭論,從而來表明Java比C(或C++)更快或效率相同。此類話題我們暫且不表(這類問題的爭論沒完沒了,也許等到我們退休的時候,還想找個活動腦筋的方式,此類問題就會是最好的選擇),我們先從如何提高Java的性能方面入手,看看怎麼做才能讓Java程序跑得更快,效率更高,吞吐量更大。

(1)不要在循環條件中計算

如果在循環(如for循環、while循環)條件中計算,則每循環一遍就要計算一次,這會降低系統效率,就比如這樣的代碼:


//每次循環都要計算count*2

while(i<count*2){

//Do Something

}

應該替換為:

//只計算一遍

int total=count*2;

while(i<total){

//Do Something

}


(2)盡可能把變量、方法聲明為final static類型

假設要將阿拉伯數字轉換為中文數字,其定義如下:


public String toChineseNum(int num){

//中文數字

Stringcns={\"零\",\"壹\",\"貳\",\"三\",\"肆\",\"伍\",\"陸\",\"柒\",\"捌\",\"玖\"};

return cns[num];

}


每次調用該方法時都會重新生成一個cns數組,注意該數組不會改變,屬於不變數組,在這種情況下,把它聲明為類變量,並且加上final static修飾會更合適,在類加載後就生成了該數組,每次方法調用則不再重新生成數組對象了,這有助於提高系統性能,代碼如下。


//聲明為類變量

final static Stringcns={\"零\",\"壹\",\"貳\",\"三\",\"肆\",\"伍\",\"陸\",\"柒\",\"捌\",\"玖\"};

public String toChineseNum(int num){

return cns[num];

}


(3)縮小變量的作用範圍

關於變量,能定義在方法內的就定義在方法內,能定義在一個循環體內的就定義在循環體內,能放置在一個try……catch塊內的就放置在該塊內,其目的是加快GC的回收。

(4)頻繁字符串操作使用StringBuilder或StringBuffer

雖然String的聯接操作(「+」號)已經做了很多優化,但在大量的追加操作上StringBuilder或StringBuffer還是比「+」號的性能好很多,例如這樣的代碼:


String str=\"Log file is ready……\";

for(int i=0;i<max;i++){

//此處生成三個對像

str+=\"log\"+i;

}

應該修改為:

StringBuilder sb=new StringBuilder(20000);

sb.append(\"Log file is ready……\");

for(int i=0;i<max;i++){

sb.append(\"log\"+i);

}

String log=sb.toString();


(5)使用非線性檢索

如果在ArrayList中存儲了大量的數據,使用indexOf查找元素會比java.utils.Collections.binarySearch的效率低很多,原因是binarySearch是二分搜索法,而indexOf使用的是逐個元素比對的方法。這裡要注意:使用binarySearch搜索時,元素必須進行排序,否則準確性就不可靠了。

(6)覆寫Exception的fillInStackTrace方法

我們在第8章中提到fillInStackTrace方法是用來記錄異常時的棧信息的,這是非常耗時的動作,如果我們在開發時不需要關注棧信息,則可以覆蓋之,如下覆蓋fillInStackTrace的自定義異常會使性能提升10倍以上:


class MyException extends Exception{

public Throwable fillInStackTrace(){

return this;

}

}


(7)不建立冗余對像

不需要建立的對象就不能建立,說起來很容易,要完全遵循此規則難度就很大了,我們經常就會無意地創建冗余對象,例如這樣一段代碼:


public void doSomething(){

//異常信息

String exceptionMsg=\"我出現異常了,快來就救我!\";

try{

Thread.sleep(10);

}catch(Exception e){

//轉換為自定義運行期異常

throw new MyException(e, exceptionMsg);

}

}


注意看變量exceptionMsg,這個字符串變量在什麼時候會被用到?只有在拋出異常時它才有用武之地,那它是什麼時候創建的呢?只要該方法被調用就創建,不管會不會拋出異常。我們知道異常不是我們的主邏輯,不是我們代碼必須或經常要到達的區域,那為了這個不經常出現的場景就每次都多定義一個字符串變量,合適嗎?而且還要佔用更多的內存!所以,在catch塊中定義exceptionMsg方法才是正道:需要的時候才創建對象。

我們知道運行一段程序需要三種資源:CPU、內存、I/O,提升CPU的處理速度可以加快代碼的執行速度,直接表現就是返回時間縮短了,效率提高了;內存是Java程序必須考慮的問題,在32位的機器上,一個JVM最多只能使用2GB的內存,而且程序佔用的內存越大,尋址效率也就越低,這也是影響效率的一個因素。I/O是程序展示和存儲數據的主要通道,如果它很緩慢就會影響正常的顯示效果。所以我們在編碼時需要從這三個方面入手接口(當然了,任何程序優化都是從這三方面入手的)。

Java的基本優化方法非常多,這裡不再羅列,相信讀者也有自己的小本本,上面所羅列的性能優化方法可能遠比這裡多,但是隨著Java的不斷升級,很多看似很正確的優化策略就逐漸過時了(或者說已經失效了),這一點還需要讀者注意。最基本的優化方法就是自我驗證,找出最佳的優化途徑,提高系統性能,不可盲目信任。