通過使用一些輔助性工具來找到程式中的瓶頸,然後就可以對瓶頸部分的代碼進行最佳化。一般有兩種方案:即最佳化代碼或更改設計方法。我們一般會選擇後者,因為不去調用以下代碼要比調用一些最佳化的代碼更能提高程式的效能。而一個設計良好的程式能夠精簡代碼,從而提高效能。 ????下面將提供一些在JAVA程式的設計和編碼中,為了能夠提高JAVA程式的效能,而經常採用的一些方法和技巧。 ????1.對象的產生和大小的調整。 ????JAVA程式設計中一個普遍的問題就是沒有好好的利用JAVA語言本身提供的函數,從而常常會產生大量的對象(或執行個體)。由於系統不僅要花時間產生對象,以後可能還需花時間對這些對象進行記憶體回收和處理。因此,產生過多的對象將會給程式的效能帶來很大的影響。 ????例1:關於String ,StringBuffer,+和append ????JAVA語言提供了對於String類型變數的操作。但如果使用不當,會給程式的效能帶來影響。如下面的語句: ????String name=new String("HuangWeiFeng"); ????System.out.println(name+"is my name"); ????看似已經很精簡了,其實並非如此。為了產生二進位的代碼,要進行如下的步驟和操作: ????(1) 產生新的字串 new String(STR_1); ????(2) 複製該字串; ????(3) 載入字串常量"HuangWeiFeng"(STR_2); ????(4) 調用字串的構架器(Constructor); ????(5) 儲存該字串到數組中(從位置0開始); ????(6) 從java.io.PrintStream類中得到靜態out變數; ????(7) 產生新的字串緩衝變數new StringBuffer(STR_BUF_1); ????(8) 複製該字串緩衝變數; ????(9) 調用字串緩衝的構架器(Constructor); ????(10) 儲存該字串緩衝到數組中(從位置1開始); ????(11) 以STR_1為參數,調用字串緩衝(StringBuffer)類中的append方法; ????(12) 載入字串常量"is my name"(STR_3); ????(13) 以STR_3為參數,調用字串緩衝(StringBuffer)類中的append方法; ????(14) 對於STR_BUF_1執行toString命令; ????(15) 調用out變數中的println方法,輸出結果。 ????由此可以看出,這兩行簡單的代碼,就產生了STR_1,STR_2,STR_3,STR_4和STR_BUF_1五個物件變數。這些產生的類的執行個體一般都存放在堆中。堆要對所有類的超類,類的執行個體進行初始化,同時還要調用類極其每個超類的構架器。而這些操作都是非常消耗系統資源的。因此,對對象的產生進行限制,是完全有必要的。 ????經修改,上面的代碼可以用如下的代碼來替換。 ????StringBuffer name=new StringBuffer("HuangWeiFeng"); ????System.out.println(name.append("is my name.").toString()); ????系統將進行如下的操作: ????(1) 產生新的字串緩衝變數new StringBuffer(STR_BUF_1); ????(2) 複製該字串緩衝變數; ????(3) 載入字串常量"HuangWeiFeng"(STR_1); ????(4) 調用字串緩衝的構架器(Constructor); ????(5) 儲存該字串緩衝到數組中(從位置1開始); ????(6) 從java.io.PrintStream類中得到靜態out變數; ????(7) 載入STR_BUF_1; ????(8) 載入字串常量"is my name"(STR_2); ????(9) 以STR_2為參數,調用字串緩衝(StringBuffer)執行個體中的append方法; ????(10) 對於STR_BUF_1執行toString命令(STR_3); ????(11)調用out變數中的println方法,輸出結果。 ????由此可以看出,經過改進後的代碼只產生了四個物件變數:STR_1,STR_2,STR_3和STR_BUF_1.你可能覺得少產生一個對象不會對程式的效能有很大的提高。但下面的程式碼片段2的執行速度將是程式碼片段1的2倍。因為程式碼片段1產生了八個對象,而程式碼片段2隻產生了四個對象。 ????程式碼片段1: ????String name= new StringBuffer("HuangWeiFeng"); ????name+="is my"; ????name+="name"; ????程式碼片段2: ????StringBuffer name=new StringBuffer("HuangWeiFeng"); ????name.append("is my"); ????name.append("name.").toString(); ????因此,充分的利用JAVA提供的庫函數來最佳化程式,對提高JAVA程式的效能時非常重要的.其注意點主要有如下幾方面; ????(1) 儘可能的使用靜態變數(Static Class Variables) ????如果類中的變數不會隨他的執行個體而變化,就可以定義為靜態變數,從而使他所有的執行個體都共用這個變數。 ????例: ????public class foo ????{ ??????SomeObject so=new SomeObject(); ????} ????就可以定義為: ????public class foo ????{ ??????static SomeObject so=new SomeObject(); ????} ????(2) 不要對已產生的對象作過多的改變。 ????對於一些類(如:String類)來講,寧願在重建一個新的對象執行個體,而不應該修改已經產生的對象執行個體。 ????例: ????String name="Huang"; ????name="Wei"; ????name="Feng"; ????上述代碼產生了三個String類型的對象執行個體。而前兩個馬上就需要系統進行記憶體回收處理。如果要對字串進行串連的操作,效能將得更差,因為系統將不得為此產生更多得臨時變數,如上例1所示。 ????(3) 產生對象時,要分配給它合理的空間和大小JAVA中的很多類都有它的預設的空間分配大小。對於StringBuffer類來講,預設的分配空間大小是16個字元。如果在程式中使用StringBuffer的空間大小不是16個字元,那麼就必須進行正確的初始化。 ????(4) 避免產生不太使用或生命週期短的對象或變數。對於這種情況,因該定義一個對象緩衝池。以為管理一個對象緩衝池的開銷要比頻繁的產生和回收對象的開銷小的多。 ????(5) 只在對象作用範圍內進行初始化。JAVA允許在代碼的任何地方定義和初始化對象。這樣,就可以只在對象作用的範圍內進行初始化。從而節約系統的開銷。 ????例: ????SomeObject so=new SomeObject(); ????If(x==1) then ????{ ??????Foo=so.getXX(); ????} ????可以修改為: ????if(x==1) then ????{ ??????SomeObject so=new SomeObject(); ??????Foo=so.getXX(); ????} ????2.異常(Exceptions) ????JAVA語言中提供了try/catch來發方便使用者捕捉異常,進行異常的處理。但是如果使用不當,也會給JAVA程式的效能帶來影響。因此,要注意以下兩點: ????(1) 避免對應用程式的邏輯使用try/catch ????如果可以用if,while等邏輯語句來處理,那麼就儘可能的不用try/catch語句。 ????(2) 重用異常 ????在必須要進行異常的處理時,要儘可能的重用已經存在的異常對象。以為在異常的處理中,產生一個異常對象要消耗掉大部分的時間。 ????3. 線程(Threading) ????一個高效能的應用程式中一般都會用到線程。因為線程能充分利用系統的資源。在其他線程因為等待硬碟或網路讀寫而 時,程式能繼續處理和運行。但是對線程運用不當,也會影響程式的效能。 ????例2:正確使用Vector類 ????Vector主要用來儲存各種類型的對象(包括相同類型和不同類型的對象)。但是在一些情況下使用會給程式帶來效能上的影響。這主要是由Vector類的兩個特點所決定的。第一,Vector提供了線程的安全保護功能。即使Vector類中的許多方法同步。但是如果你已經確認你的應用程式是單線程,這些方法的同步就完全不必要了。第二,在Vector尋找儲存的各種對象時,常常要花很多的時間進行類型的匹配。而當這些對象都是同一類型時,這些匹配就完全不必要了。因此,有必要設計一個單線程的,儲存特定類型對象的類或集合來替代Vector類.用來替換的程式如下(StringVector.java): ????public class StringVector ????{ ??????private String [] data; ??????private int count; ??????public StringVector() ??????{ ????????this(10); // default size is 10 ??????} ??????public StringVector(int initialSize) ??????{ ????????data = new String[initialSize]; ??????} ??????public void add(String str) ??????{ ??????// ignore null strings ??????if(str == null) { return; } ??????ensureCapacity(count + 1); ??????data[count++] = str; ??????} ??????private void ensureCapacity(int minCapacity) ??????{ ????????int oldCapacity = data.length; ????????if (minCapacity > oldCapacity) ????????{ ??????????String oldData[] = data; ??????????int newCapacity = oldCapacity * 2; ??????????data = new String[newCapacity]; ??????????System.arraycopy(oldData, 0, data, 0, count); ????????} ??????} ??????public void remove(String str) ??????{ ??????if(str == null) { return; // ignore null str } ??????for(int i = 0; i < count; i++) ??????{ ????????// check for a match ????????if(data[i].equals(str)) ????????{ ??????????System.arraycopy(data,i+1,data,i,count-1); // copy data ??????????// allow previously valid array element be gc´d ??????????data[--count] = null; ??????????return; ????????} ??????} ??????} ??????public final String getStringAt(int index) ??????{ ??????if(index < 0) { return null; } ??????else if(index > count) { return null; // index is > # strings } ??????else { return data[index]; // index is good } ??????} ????} ????因此,代碼: ????Vector Strings=new Vector(); ????Strings.add("One"); ????Strings.add("Two"); ????String Second=(String)Strings.elementAt(1); ????可以用如下的代碼替換: ????StringVector Strings=new StringVector(); ????Strings.add("One"); ????Strings.add("Two"); ????String Second=Strings.getStringAt(1); ????這樣就可以通過最佳化線程來提高JAVA程式的效能。用於測試的程式如下(TestCollection.java): ????import java.util.Vector; ????public class TestCollection ????{ ??????public static void main(String args []) ??????{ ????????TestCollection collect = new TestCollection(); ????????if(args.length == 0) ????????{ ??????????System.out.println("Usage: java TestCollection [ vector | stringvector ]"); ??????????System.exit(1); ????????} ????????if(args[0].equals("vector")) ????????{ ??????????Vector store = new Vector(); ??????????long start = System.currentTimeMillis(); ??????????for(int i = 0; i < 1000000; i++) ??????????{ ????????????store.addElement("string"); ??????????} ??????????long finish = System.currentTimeMillis(); ??????????System.out.println((finish-start)); ??????????start = System.currentTimeMillis(); ??????????for(int i = 0; i < 1000000; i++) ??????????{ ????????????String result = (String)store.elementAt(i); ??????????} ??????????finish = System.currentTimeMillis(); ??????????System.out.println((finish-start)); ????????} ????????else if(args[0].equals("stringvector")) ????????{ ??????????StringVector store = new StringVector(); ??????????long start = System.currentTimeMillis(); ??????????for(int i = 0; i < 1000000; i++) { store.add("string"); } ??????????long finish = System.currentTimeMillis(); ??????????System.out.println((finish-start)); ??????????start = System.currentTimeMillis(); ??????????for(int i = 0; i < 1000000; i++) { ????????????String result = store.getStringAt(i); ??????????} ??????????finish = System.currentTimeMillis(); ??????????System.out.println((finish-start)); ????????} ??????} ????} ????關於線程的操作,要注意如下幾個方面: ????(1) 防止過多的同步 ????如上所示,不必要的同步常常會造成程式效能的下降。因此,如果程式是單線程,則一定不要使用同步。 ????(2) 同步方法而不要同步整個程式碼片段 ????對某個方法或函數進行同步比對整個程式碼片段進行同步的效能要好。 ????(3) 對每個對象使用多”鎖”的機制來增大並發。 ????一般每個對象都只有一個”鎖”,這就表明如果兩個線程執行一個對象的兩個不同的同步方法時,會發生”死結”。即使這兩個方法並不共用任何資源。為了避免這個問題,可以對一個對象實行”多鎖”的機制。如下所示: ????class foo ????{ ??????private static int var1; ??????private static Object lock1=new Object(); ??????private static int var2; ??????private static Object lock2=new Object(); ??????public static void increment1() ??????{ ????????synchronized(lock1) ????????{ ??????????var1++; ????????} ??????} ??????public static void increment2() ??????{ ????????synchronized(lock2) ????????{ ??????????var2++; ????????} ??????} ????} ????4.輸入和輸出(I/O) ????輸入和輸出包括很多方面,但涉及最多的是對硬碟,網路或資料庫的讀寫操作。對於讀寫操作,又分為有緩衝和沒有緩衝的;對於資料庫的操作,又可以有多種類型的JDBC磁碟機可以選擇。但無論怎樣,都會給程式的效能帶來影響。因此,需要注意如下幾點: ????(1) 使用輸入輸出緩衝 ????儘可能的多使用緩衝。但如果要經常對緩衝進行重新整理(flush),則建議不要使用緩衝。 ????(2) 輸出資料流(Output Stream)和Unicode字串 當時用Output Stream和Unicode字串時,Write類的開銷比較大。因為它要實現Unicode到位元組(byte)的轉換.因此,如果可能的話,在使用Write類之前就實現轉換或用OutputStream類代替Writer類來使用。 ????(3) 當需序列化時使用transient 當序列化一個類或對象時,對於那些原子類型(atomic)或可以重建的原素要表識為transient類型。這樣就不用每一次都進行序列化。如果這些序列化的對象要在網路上傳輸,這一小小的改變對效能會有很大的提高。 ????(4) 使用快取(Cache) 對於那些經常要使用而又不大變化的對象或資料,可以把它儲存在快取中。這樣就可以提高訪問的速度。這一點對於從資料庫中返回的結果集尤其重要。 ????(5) 使用速度快的JDBC磁碟機(Driver) JAVA對訪問資料庫提供了四種方法。這其中有兩種是JDBC磁碟機。一種是用JAVA外包的本地磁碟機;另一種是完全的JAVA磁碟機。具體要使用哪一種得根據JAVA布署的環境和應用程式本身來定。 ????5.一些其他的經驗和技巧 ????(1) 使用局部變數。 ????(2) 避免在同一個類中動過調用函數或方法(get或set)來設定或調用變數。 ????(3) 避免在迴圈中產生同一個變數或調用同一個函數(參數變數也一樣)。 ????(4) 儘可能的使用static,final,private等關鍵字。 ????(5) 當複製大量資料時,使用System.arraycopy()命令。 |