調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧 (Stack) 裏面,讀寫速度較快。其餘變量,如靜態變量、實例變量等,都在堆 (heap) 中建立,讀寫速度較慢。清單 12 所示代碼演示了使用局部變量和靜態變量的操做時間對比。數組
public class variableCompare { public static int b = 0; public static void main(String[] args){ int a = 0; long starttime = System.currentTimeMillis(); for(int i=0;i<1000000;i++){ a++;//在函數體內定義局部變量 } System.out.println(System.currentTimeMillis() - starttime); starttime = System.currentTimeMillis(); for(int i=0;i<1000000;i++){ b++;//在函數體內定義局部變量 } System.out.println(System.currentTimeMillis() - starttime); } }
運行後輸出如清單 13 所示。dom
0 15
以上兩段代碼的運行時間分別爲 0ms 和 15ms。因而可知,局部變量的訪問速度遠遠高於類的成員變量。函數
位運算是全部的運算中最爲高效的。所以,能夠嘗試使用位運算代替部分算數運算,來提升系統的運行速度。最典型的就是對於整數的乘除運算優化。清單 14 所示代碼是一段使用算數運算的實現。oop
public class yunsuan { public static void main(String args[]){ long start = System.currentTimeMillis(); long a=1000; for(int i=0;i<10000000;i++){ a*=2; a/=2; } System.out.println(a); System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); for(int i=0;i<10000000;i++){ a<<=1; a>>=1; } System.out.println(a); System.out.println(System.currentTimeMillis() - start); } }
運行輸出如清單 15 所示。性能
1000 546 1000 63
兩段代碼執行了徹底相同的功能,在每次循環中,整數 1000 乘以 2,而後除以 2。第一個循環耗時 546ms,第二個循環耗時 63ms。優化
關鍵字 switch 語句用於多條件判斷,switch 語句的功能相似於 if-else 語句,二者的性能差很少。可是 switch 語句有性能提高空間。清單 16 所示代碼演示了 Switch 與 if-else 之間的對比。spa
public class switchCompareIf { public static int switchTest(int value){ int i = value%10+1; switch(i){ case 1:return 10; case 2:return 11; case 3:return 12; case 4:return 13; case 5:return 14; case 6:return 15; case 7:return 16; case 8:return 17; case 9:return 18; default:return -1; } } public static int arrayTest(int[] value,int key){ int i = key%10+1; if(i>9 || i<1){ return -1; }else{ return value[i]; } } public static void main(String[] args){ int chk = 0; long start=System.currentTimeMillis(); for(int i=0;i<10000000;i++){ chk = switchTest(i); } System.out.println(System.currentTimeMillis()-start); chk = 0; start=System.currentTimeMillis(); int[] value=new int[]{0,10,11,12,13,14,15,16,17,18}; for(int i=0;i<10000000;i++){ chk = arrayTest(value,i); } System.out.println(System.currentTimeMillis()-start); } }
運行輸出如清單 17 所示。設計
172 93
使用一個連續的數組代替 switch 語句,因爲對數據的隨機訪問很是快,至少好於 switch 的分支判斷,從上面例子能夠看到比較的效率差距近乎 1 倍,switch 方法耗時 172ms,if-else 方法耗時 93ms。指針
JDK 不少類庫是採用數組方式實現的數據存儲,好比 ArrayList、Vector 等,數組的優勢是隨機訪問性能很是好。一維數組和二維數組的訪問速度不同,一維數組的訪問速度要優於二維數組。在性能敏感的系統中要使用二維數組,儘可能 將二維數組轉化爲一維數組再進行處理,以提升系統的響應速度。code
public class arrayTest { public static void main(String[] args){ long start = System.currentTimeMillis(); int[] arraySingle = new int[1000000]; int chk = 0; for(int i=0;i<100;i++){ for(int j=0;j<arraySingle.length;j++){ arraySingle[j] = j; } } for(int i=0;i<100;i++){ for(int j=0;j<arraySingle.length;j++){ chk = arraySingle[j]; } } System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); int[][] arrayDouble = new int[1000][1000]; chk = 0; for(int i=0;i<100;i++){ for(int j=0;j<arrayDouble.length;j++){ for(int k=0;k<arrayDouble[0].length;k++){ arrayDouble[i][j]=j; } } } for(int i=0;i<100;i++){ for(int j=0;j<arrayDouble.length;j++){ for(int k=0;k<arrayDouble[0].length;k++){ chk = arrayDouble[i][j]; } } } System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); arraySingle = new int[1000000]; int arraySingleSize = arraySingle.length; chk = 0; for(int i=0;i<100;i++){ for(int j=0;j<arraySingleSize;j++){ arraySingle[j] = j; } } for(int i=0;i<100;i++){ for(int j=0;j<arraySingleSize;j++){ chk = arraySingle[j]; } } System.out.println(System.currentTimeMillis() - start); start = System.currentTimeMillis(); arrayDouble = new int[1000][1000]; int arrayDoubleSize = arrayDouble.length; int firstSize = arrayDouble[0].length; chk = 0; for(int i=0;i<100;i++){ for(int j=0;j<arrayDoubleSize;j++){ for(int k=0;k<firstSize;k++){ arrayDouble[i][j]=j; } } } for(int i=0;i<100;i++){ for(int j=0;j<arrayDoubleSize;j++){ for(int k=0;k<firstSize;k++){ chk = arrayDouble[i][j]; } } } System.out.println(System.currentTimeMillis() - start); } }
運行輸出如清單 19 所示。
343 624 287 390
第一段代碼操做的是一維數組的賦值、取值過程,第二段代碼操做的是二維數組的賦值、取值過程。能夠看到一維數組方式比二維數組方式快接近一半時間。而對於數組內若是能夠減小賦值運算,則能夠進一步減小運算耗時,加快程序運行速度。
大部分狀況下,代碼的重複勞動因爲計算機的高速運行,並不會對性能構成太大的威脅,但若但願將系統性能發揮到極致,仍是有不少地方能夠優化的。
public class duplicatedCode { public static void beforeTuning(){ long start = System.currentTimeMillis(); double a1 = Math.random(); double a2 = Math.random(); double a3 = Math.random(); double a4 = Math.random(); double b1,b2; for(int i=0;i<10000000;i++){ b1 = a1*a2*a4/3*4*a3*a4; b2 = a1*a2*a3/3*4*a3*a4; } System.out.println(System.currentTimeMillis() - start); } public static void afterTuning(){ long start = System.currentTimeMillis(); double a1 = Math.random(); double a2 = Math.random(); double a3 = Math.random(); double a4 = Math.random(); double combine,b1,b2; for(int i=0;i<10000000;i++){ combine = a1*a2/3*4*a3*a4; b1 = combine*a4; b2 = combine*a3; } System.out.println(System.currentTimeMillis() - start); } public static void main(String[] args){ duplicatedCode.beforeTuning(); duplicatedCode.afterTuning(); } }
運行輸出如清單 21 所示。
202 110
兩段代碼的差異是提取了重複的公式,使得這個公式的每次循環計算只執行一次。分別耗時 202ms 和 110ms,可見,提取複雜的重複操做是至關具備意義的。這個例子告訴咱們,在循環體內,若是可以提取到循環體外的計算公式,最好提取出來,儘量讓程序 少作重複的計算。
當性能問題成爲系統的主要矛盾時,能夠嘗試優化循環,例如減小循環次數,這樣也許能夠加快程序運行速度。
public class reduceLoop { public static void beforeTuning(){ long start = System.currentTimeMillis(); int[] array = new int[9999999]; for(int i=0;i<9999999;i++){ array[i] = i; } System.out.println(System.currentTimeMillis() - start); } public static void afterTuning(){ long start = System.currentTimeMillis(); int[] array = new int[9999999]; for(int i=0;i<9999999;i+=3){ array[i] = i; array[i+1] = i+1; array[i+2] = i+2; } System.out.println(System.currentTimeMillis() - start); } public static void main(String[] args){ reduceLoop.beforeTuning(); reduceLoop.afterTuning(); } }
運行輸出如清單 23 所示。
265 31
這個例子能夠看出,經過減小循環次數,耗時縮短爲原來的 1/8。
雖 然位運算的速度遠遠高於算術運算,可是在條件判斷時,使用位運算替代布爾運算確實是很是錯誤的選擇。在條件判斷時,Java 會對布爾運算作至關充分的優化。假設有表達式 a、b、c 進行布爾運算「a&&b&&c」,根據邏輯與的特色,只要在整個布爾表達式中有一項返回 false,整個表達式就返回 false,所以,當表達式 a 爲 false 時,該表達式將當即返回 false,而不會再去計算表達式 b 和 c。若此時,表達式 a、b、c 須要消耗大量的系統資源,這種處理方式能夠節省這些計算資源。同理,當計算表達式「a||b||c」時,只要 a、b 或 c,3 個表達式其中任意一個計算結果爲 true 時,總體表達式當即返回 true,而不去計算剩餘表達式。簡單地說,在布爾表達式的計算中,只要表達式的值能夠肯定,就會當即返回,而跳過剩餘子表達式的計算。若使用位運算 (按位與、按位或) 代替邏輯與和邏輯或,雖然位運算自己沒有性能問題,可是位運算老是要將全部的子表達式所有計算完成後,再給出最終結果。所以,從這個角度看,使用位運算替 代布爾運算會使系統進行不少無效計算。
public class OperationCompare { public static void booleanOperate(){ long start = System.currentTimeMillis(); boolean a = false; boolean b = true; int c = 0; //下面循環開始進行位運算,表達式裏面的全部計算因子都會被用來計算 for(int i=0;i<1000000;i++){ if(a&b&"Test_123".contains("123")){ c = 1; } } System.out.println(System.currentTimeMillis() - start); } public static void bitOperate(){ long start = System.currentTimeMillis(); boolean a = false; boolean b = true; int c = 0; //下面循環開始進行布爾運算,只計算表達式 a 便可知足條件 for(int i=0;i<1000000;i++){ if(a&&b&&"Test_123".contains("123")){ c = 1; } } System.out.println(System.currentTimeMillis() - start); } public static void main(String[] args){ OperationCompare.booleanOperate(); OperationCompare.bitOperate(); } }
運行輸出如清單 25 所示。
63 0
實例顯示布爾計算大大優於位運算,可是,這個結果不能說明位運算比邏輯運算慢,由於在全部的邏輯與運算中,都省略了表達式「"Test_123".contains("123")」的計算,而全部的位運算都沒能省略這部分系統開銷。
數 據複製是一項使用頻率很高的功能,JDK 中提供了一個高效的 API 來實現它。System.arraycopy() 函數是 native 函數,一般 native 函數的性能要優於普通的函數,因此,僅處於性能考慮,在軟件開發中,應儘量調用 native 函數。ArrayList 和 Vector 大量使用了 System.arraycopy 來操做數據,特別是同一數組內元素的移動及不一樣數組之間元素的複製。arraycopy 的本質是讓處理器利用一條指令處理一個數組中的多條記錄,有點像彙編語言裏面的串操做指令 (LODSB、LODSW、LODSB、STOSB、STOSW、STOSB),只需指定頭指針,而後開始循環便可,即執行一次指令,指針就後移一個位 置,操做多少數據就循環多少次。若是在應用程序中須要進行數組複製,應該使用這個函數,而不是本身實現。具體應用如清單 26 所示。
public class arrayCopyTest { public static void arrayCopy(){ int size = 10000000; int[] array = new int[size]; int[] arraydestination = new int[size]; for(int i=0;i<array.length;i++){ array[i] = i; } long start = System.currentTimeMillis(); for(int j=0;j>1000;j++){ System.arraycopy(array, 0, arraydestination, 0, size);//使用 System 級別的本地 arraycopy 方式 } System.out.println(System.currentTimeMillis() - start); } public static void arrayCopySelf(){ int size = 10000000; int[] array = new int[size]; int[] arraydestination = new int[size]; for(int i=0;i<array.length;i++){ array[i] = i; } long start = System.currentTimeMillis(); for(int i=0;i<1000;i++){ for(int j=0;j<size;j++){ arraydestination[j] = array[j];//本身實現的方式,採用數組的數據互換方式 } } System.out.println(System.currentTimeMillis() - start); } public static void main(String[] args){ arrayCopyTest.arrayCopy(); arrayCopyTest.arrayCopySelf(); } }
輸出如清單 27 所示。
0 23166
上面的例子顯示採用 arraycopy 方法執行復制會很是的快。緣由就在於 arraycopy 屬於本地方法,源代碼如清單 28 所示。
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
src - 源數組;srcPos - 源數組中的起始位置; dest - 目標數組;destPos - 目標數據中的起始位置;length - 要複製的數組元素的數量。清單 28 所示方法使用了 native 關鍵字,調用的爲 C++編寫的底層函數,可見其爲 JDK 中的底層函數。
Java 程序設計優化有不少方面能夠入手,做者將以系列的方式逐步介紹覆蓋全部領域。本文是該系列的第一篇文章,主要介紹了字符串對象操做相關、數據定義方面的優 化方案、運算邏輯優化及建議,從實際代碼演示入手,對優化建議及方案進行了驗證。做者始終堅信,沒有什麼優化方案是百分百有效的,須要讀者根據實際狀況進 行選擇、實踐。