程序的性能受代碼質量的直接影響。在本文中,主要介紹一些代碼編寫的小技巧和慣例,這些技巧有助於在代碼級別上提高系統性能。程序員
一、慎用異常算法
在Java軟件開發中,常用 try-catch 進行錯誤捕獲,可是,try-catch 語句對系統性能而言是很是糟糕的。雖然在一次 try-catch中,沒法察覺到它對性能帶來的損失,可是,一旦try-catch被應用於循環之中,就會給系統性能帶來極大的傷害。數組
如下是一段將try-catch應用於for循環內的示例架構
這段代碼我運行時間是 27211 ms。若是將try-catch移到循環體外,那麼就能提高系統性能,以下代碼app
運行耗時 15647 ms。可見tyr-catch對系統性能的影響。函數
二、使用局部環境工具
調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧(Stack)中,速度較快。其餘變量,如靜態變量、實例變量等,都在堆(Heap)中建立,速度較慢。性能
下面是一段測試用例學習
// private static int a = 0;測試
public static void main(String[] args) { int a = 0; long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) {
a = a + 1;
System.out.println(i);
}
System.out.println(System.currentTimeMillis() - start);
}
運行結果很明顯,使用靜態變量耗時15677ms,使用局部變量耗時13509ms。因而可知,局部變量的訪問速度高於類的成員變量。
三、位運算代替乘除法
在全部的運算中,位運算是最爲高效的。所以,能夠嘗試使用位運算代替部分算術運算,來提升系統的運行速度。
好比在HashMap的源碼中使用了位運算
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
對於整數的乘除運算優化
a*=2
a/=2
用位運算能夠寫爲
a<<=1a>>=1
四、替換switch
關鍵字 switch 語句用於多條件判斷, switch 語句的功能相似於 if-else 語句,二者性能也差很少。所以,不能說 switch 語句會下降系統的性能。可是,在絕大部分狀況下,switch 語句仍是有性能提高空間的。
來看下面的例子:
public static void main(String[] args) { long start = System.currentTimeMillis(); int re = 0; for (int i = 0;i<1000000;i++){
re = switchInt(i);
System.out.println(re);
}
System.out.println(System.currentTimeMillis() - start+"毫秒");//17860
} public static int switchInt(int z){ int i = z%10+1; switch (i){ case 1:return 3; case 2:return 6; case 3:return 7; case 4:return 8; case 5:return 10; case 6:return 16; case 7:return 18; case 8:return 44; default:return -1;
}
}
就分支邏輯而言,這種 switch 模式的性能並不差。可是若是換一種新的思路替代switch,實現相同的程序功能,性能就能有很大的提高空間。
public static void main(String[] args) { long start = System.currentTimeMillis(); int re = 0; int[] sw = new int[]{0,3,6,7,8,10,16,18,44}; for (int i = 0;i<1000000;i++){
re = arrayInt(sw,i);
System.out.println(re);
}
System.out.println(System.currentTimeMillis() - start+"毫秒");//12590
}
public static int arrayInt( int[] sw,int z){ int i = z%10+1; if (i>7 || i<1){ return -1;
}else { return sw[i];
}
}
以上代碼使用全新的思路,使用一個連續的數組代替了 switch 語句。由於對數據的隨機訪問是很是快的,至少好於 switch 的分支判斷。經過實驗,使用switch的語句耗時17860ms,使用數組的實現只耗時12590ms,提高了5s多。在軟件開發中,換一種思路可能會取得更好的效果,好比使用數組替代switch語句就是就是一個很好的例子。在此我向你們推薦一個架構學習交流裙。交流學習裙號:821169538,裏面會分享一些資深架構師錄製的視頻錄像
五、一維數組代替二維數組
因爲數組的隨機訪問的性能很是好,許多JDK類庫,如ArrayList、Vector等都是使用了數組做爲其數組實現。可是,做爲軟件開發人員也必須知道,一位數組和二維數組的訪問速度是不同的。一位數組的訪問速度要優於二維數組。所以,在性能敏感的系統中要使用二維數組的,能夠嘗試經過可靠地算法,將二維數組轉爲一維數組再進行處理,以提升系統的響應速度。
六、提取表達式
在軟件開發過程當中,程序員很容易有意無心讓代碼作一些「重複勞動」,在大部分狀況下,因爲計算機的告訴運行,這些「重複勞動」並不會對性能構成太大的威脅,但若將系統性能發揮到極致,提取這些「重複勞動」至關有意義。
來看下面的測試用例:
@Test public void test(){ long start = System.currentTimeMillis();
ArrayList list = new ArrayList(); for (int i = 0;i<100000;i++){
System.out.println(list.add(i));
} //以上是爲了作準備
for (int i = 0;i
System.out.println(list.get(i));
}
System.out.println(System.currentTimeMillis() - start);//5444
}
若是咱們把list.size()方法提取出來,優化後的代碼以下:
@Test public void test(){ long start = System.currentTimeMillis();
ArrayList list = new ArrayList(); for (int i = 0;i<100000;i++){
System.out.println(list.add(i));
} //以上是爲了作準備
int n = list.size(); for (int i = 0;i
System.out.println(list.get(i));
}
System.out.println(System.currentTimeMillis() - start);//3514
}
在個人機器上,前者耗時5444ms,後者耗時3514ms,相差2s左右,可見,提取重複的操做是至關有意義的。
七、展開循環
與前面所介紹的優化技巧略有不一樣,筆者認爲展開循環是一種在極端狀況下使用的優化手段,由於展開循環極可能會影響代碼的可讀性和可維護性,而這二者對軟件系統來講也是極爲重要的。可是,當性能問題成爲系統主要矛盾時,展開循環絕對是一種值得嘗試的技術。
八、布爾運算代替位運算
雖然位運算的速度遠遠高於算術運算,可是在條件判斷時,使用位運算替代布爾運算倒是很是錯誤的選擇。
在條件判斷時,Java會對布爾運算作至關充分的優化。假設有表達式 a,b,c 進行布爾運算「a&&b&&c」 ,根據邏輯與的特色,只要在整個布爾表達式中有一項返回false,整個表達式就返回false,所以,當表達式a爲false時,該表達式將當即返回 false ,而不會再去計算表達式b 和c。同理,當計算表達式爲「a||b||c」時,也是同樣。
若使用位運算(按位與」&「、按位或」|「)代替邏輯與和邏輯或,雖然位運算自己沒有性能問題,可是位運算老是要將全部的子表達式所有計算完成後,再給出最終結果。所以,從這個角度來講,使用位運算替代布爾運算會使系統進行不少無效計算。
九、使用arrayCopy()
數組複製是一項使用頻率很高的功能,JDK中提供了一個高效的API來實現它:
若是在應用程序須要進行數組複製,應該使用這個函數,而不是本身實現。
方法代碼:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos, int length);
它的用法是將源數組 src 從索引 srcPos 處複製到目標數組 dest 的 索引destPos處,複製的長度爲 length。
System.arraycopy() 方法是 native 方法,一般 native 方法的性能要優於普通的方法。僅出於性能考慮,在軟件開發中,儘量調用 native 方法。
十、使用Buffer進行I/O流操做
除NIO外,使用 Java 進行 I/O操做有兩種基本方法:
使用基於InputStream 和 OutputStream 的方式;(字節流)
使用 Writer 和 Reader。(字符流)
不管使用哪一種方式進行文件 I/O,若是能合理地使用緩衝,就能有效的提升I/O的性能。
十一、使用clone()代替new
在Java中新建對象實例最經常使用的方法是使用 new 關鍵字。JDK對 new 的支持很是好,使用 new 關鍵字建立輕量級對象時,速度很是快。可是,對於重量級對象,因爲對象在構造函數中可能會進行一些複雜且耗時的操做,所以,構造函數的執行時間可能會比較長。致使系統短時間內沒法得到大量的實例。爲了解決這個問題,可使用Object.clone() 方法。
Object.clone() 方法能夠繞過構造函數,快速複製一個對象實例。可是,在默認狀況下,clone()方法生成的實例只是原對象的淺拷貝。
這裏不得不提Java只有值傳遞了,關於這點,個人理解是基本數據類型引用的是值,普通對象引用的也是值,不過這個普通對象引用的值實際上是一個對象的地址。代碼示例:在此我向你們推薦一個架構學習交流裙。交流學習裙號:821169538,裏面會分享一些資深架構師錄製的視頻錄像
int i = 0; int j = i; //i的值是0
User user1 = new User();
User user2 = user1; //user1值是new User()的內存地址
若是須要深拷貝,則須要從新實現 clone() 方法。下面看一下ArrayList實現的clone()方法:
public Object clone() { try {
ArrayListv = (ArrayList) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0; return v;
} catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
在ArrayList的clone()方法中,首先使用 super.clone() 方法生成一份淺拷貝對象。而後拷貝一份新的elementData數組讓新的ArrayList去引用。使克隆後的ArrayList對象與原對象持有不一樣的引用,實現了深拷貝。
十二、靜態方法替代實例方法
使用 static 關鍵字描述的方法爲靜態方法。在Java中,因爲實例方法須要維護一張相似虛函數表的結構,以實現對多態的支持。與靜態方法相比,實例方法的調用須要更多的資源。所以,對於一些經常使用的工具類方法,沒有對其進行重載的必要,那麼將它們聲明爲 static,即可以加速方法的調用。同時,調用 static 方法不須要生成類的實例。比調用實例方法更爲方便、易用。