Java 程序優化:字符串操做、基本運算方法等優化策略(二)

數據定義、運算邏輯優化

使用局部變量

調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧 (Stack) 裏面,讀寫速度較快。其餘變量,如靜態變量、實例變量等,都在堆 (heap) 中建立,讀寫速度較慢。清單 12 所示代碼演示了使用局部變量和靜態變量的操做時間對比。數組

清單 12. 局部變量 VS 靜態變量
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

清單 13. 運行結果
0
15

以上兩段代碼的運行時間分別爲 0ms 和 15ms。因而可知,局部變量的訪問速度遠遠高於類的成員變量。函數

位運算代替乘除法

位運算是全部的運算中最爲高效的。所以,能夠嘗試使用位運算代替部分算數運算,來提升系統的運行速度。最典型的就是對於整數的乘除運算優化。清單 14 所示代碼是一段使用算數運算的實現。oop

清單 14. 算數運算
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 所示。性能

清單 15. 運行結果
1000
546
1000
63

兩段代碼執行了徹底相同的功能,在每次循環中,整數 1000 乘以 2,而後除以 2。第一個循環耗時 546ms,第二個循環耗時 63ms。優化

替換 switch

關鍵字 switch 語句用於多條件判斷,switch 語句的功能相似於 if-else 語句,二者的性能差很少。可是 switch 語句有性能提高空間。清單 16 所示代碼演示了 Switch 與 if-else 之間的對比。spa

清單 16.Switch 示例
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 所示。設計

清單 17. 運行結果
172
93

使用一個連續的數組代替 switch 語句,因爲對數據的隨機訪問很是快,至少好於 switch 的分支判斷,從上面例子能夠看到比較的效率差距近乎 1 倍,switch 方法耗時 172ms,if-else 方法耗時 93ms。指針

一維數組代替二維數組

JDK 不少類庫是採用數組方式實現的數據存儲,好比 ArrayList、Vector 等,數組的優勢是隨機訪問性能很是好。一維數組和二維數組的訪問速度不同,一維數組的訪問速度要優於二維數組。在性能敏感的系統中要使用二維數組,儘可能 將二維數組轉化爲一維數組再進行處理,以提升系統的響應速度。code

清單 18. 數組方式對比
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 所示。

清單 19. 運行結果
343
624
287
390

第一段代碼操做的是一維數組的賦值、取值過程,第二段代碼操做的是二維數組的賦值、取值過程。能夠看到一維數組方式比二維數組方式快接近一半時間。而對於數組內若是能夠減小賦值運算,則能夠進一步減小運算耗時,加快程序運行速度。

提取表達式

大部分狀況下,代碼的重複勞動因爲計算機的高速運行,並不會對性能構成太大的威脅,但若但願將系統性能發揮到極致,仍是有不少地方能夠優化的。

清單 20. 提取表達式
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 所示。

清單 21. 運行結果
202
110

兩段代碼的差異是提取了重複的公式,使得這個公式的每次循環計算只執行一次。分別耗時 202ms 和 110ms,可見,提取複雜的重複操做是至關具備意義的。這個例子告訴咱們,在循環體內,若是可以提取到循環體外的計算公式,最好提取出來,儘量讓程序 少作重複的計算。

優化循環

當性能問題成爲系統的主要矛盾時,能夠嘗試優化循環,例如減小循環次數,這樣也許能夠加快程序運行速度。

清單 22. 減小循環次數
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 所示。

清單 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,而不去計算剩餘表達式。簡單地說,在布爾表達式的計算中,只要表達式的值能夠肯定,就會當即返回,而跳過剩餘子表達式的計算。若使用位運算 (按位與、按位或) 代替邏輯與和邏輯或,雖然位運算自己沒有性能問題,可是位運算老是要將全部的子表達式所有計算完成後,再給出最終結果。所以,從這個角度看,使用位運算替 代布爾運算會使系統進行不少無效計算。

清單 24. 運算方式對比
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 所示。

清單 25. 運行結果
63
0

實例顯示布爾計算大大優於位運算,可是,這個結果不能說明位運算比邏輯運算慢,由於在全部的邏輯與運算中,都省略了表達式「"Test_123".contains("123")」的計算,而全部的位運算都沒能省略這部分系統開銷。

使用 arrayCopy()

數 據複製是一項使用頻率很高的功能,JDK 中提供了一個高效的 API 來實現它。System.arraycopy() 函數是 native 函數,一般 native 函數的性能要優於普通的函數,因此,僅處於性能考慮,在軟件開發中,應儘量調用 native 函數。ArrayList 和 Vector 大量使用了 System.arraycopy 來操做數據,特別是同一數組內元素的移動及不一樣數組之間元素的複製。arraycopy 的本質是讓處理器利用一條指令處理一個數組中的多條記錄,有點像彙編語言裏面的串操做指令 (LODSB、LODSW、LODSB、STOSB、STOSW、STOSB),只需指定頭指針,而後開始循環便可,即執行一次指令,指針就後移一個位 置,操做多少數據就循環多少次。若是在應用程序中須要進行數組複製,應該使用這個函數,而不是本身實現。具體應用如清單 26 所示。

清單 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 所示。

清單 27. 運行結果
0
23166

上面的例子顯示採用 arraycopy 方法執行復制會很是的快。緣由就在於 arraycopy 屬於本地方法,源代碼如清單 28 所示。

清單 28.arraycopy 方法
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 程序設計優化有不少方面能夠入手,做者將以系列的方式逐步介紹覆蓋全部領域。本文是該系列的第一篇文章,主要介紹了字符串對象操做相關、數據定義方面的優 化方案、運算邏輯優化及建議,從實際代碼演示入手,對優化建議及方案進行了驗證。做者始終堅信,沒有什麼優化方案是百分百有效的,須要讀者根據實際狀況進 行選擇、實踐。

相關文章
相關標籤/搜索