程序的性能受到代碼質量的直接影響。此次主要介紹一些代碼編寫的小技巧和慣例。雖然看起來有些是微不足道的編程技巧,卻可能爲系統性能帶來成倍的提高,所以仍是值得關注的。java
在Java開發中,常用try-catch進行錯誤捕獲,可是try-catch語句對系統性能而言是很是糟糕的。雖然一次try-catch中,沒法察覺到她對性能帶來的損失,可是一旦try-catch語句被應用於循環或是遍歷體內,就會給系統性能帶來極大的傷害。程序員
如下是一段將try-catch應用於循環體內的示例代碼:編程
@Test public void test11() { long start = System.currentTimeMillis(); int a = 0; for(int i=0;i<1000000000;i++){ try { a++; }catch (Exception e){ e.printStackTrace(); } } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
上面這段代碼運行結果是:數組
useTime:10
下面是一段將try-catch移到循環體外的代碼,那麼性能就提高了將近一半。以下:dom
@Test public void test(){ long start = System.currentTimeMillis(); int a = 0; try { for (int i=0;i<1000000000;i++){ a++; } }catch (Exception e){ e.printStackTrace(); } long useTime = System.currentTimeMillis()-start; System.out.println(useTime); }
運行結果:ide
useTime:6
調用方法時傳遞的參數以及在調用中建立的臨時變量都保存在棧(Stack)中,速度快。其餘變量,如靜態變量、實例變量等,都在堆(Heap)中建立,速度較慢。函數
下面是一段使用局部變量進行計算的代碼:性能
@Test public void test11() { long start = System.currentTimeMillis(); int a = 0; for(int i=0;i<1000000000;i++){ a++; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:優化
useTime:5
將局部變量替換爲類的靜態變量:spa
static int aa = 0; @Test public void test(){ long start = System.currentTimeMillis(); for (int i=0;i<1000000000;i++){ aa++; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:94
經過上面兩次的運行結果,能夠看出來局部變量的訪問速度遠遠高於類成員變量。
在全部的運算中,位運算是最爲高效的。所以,能夠嘗試使用位運算代替部分算術運算,來提升系統的運行速度。最典型的就是對於整數的乘除運算優化。
下面是一段使用算術運算的代碼:
@Test public void test11() { long start = System.currentTimeMillis(); int a = 0; for(int i=0;i<1000000000;i++){ a*=2; a/=2; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:1451
將循環體中的乘除運算改成等價的位運算,代碼以下:
@Test public void test(){ long start = System.currentTimeMillis(); int aa = 0; for (int i=0;i<1000000000;i++){ aa<<=1; aa>>=1; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:10
上兩段代碼執行了徹底相同的功能,在每次循環中,都將整數乘以2,併除以2。可是運行結果耗時相差很是大,因此位運算的效率仍是顯而易見的。
在軟件開發過程當中,程序員很容易有意無心地讓代碼作一些「重複勞動」,在大部分狀況下,因爲計算機的高速運行,這些「重複勞動」並不會對性能構成太大的威脅,但若但願將系統性能發揮到極致,提取這些「重複勞動」至關有意義。
好比如下代碼中進行了兩次算術計算:
@Test public void testExpression(){ long start = System.currentTimeMillis(); double d = Math.random(); double a = Math.random(); double b = Math.random(); double e = Math.random(); double x,y; for(int i=0;i<10000000;i++){ x = d*a*b/3*4*a; y = e*a*b/3*4*a; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:21
仔細看能發現,兩個計算表達式的後半部分徹底相同,這也意味着在每次循環中,相同部分的表達式被從新計算了。
那麼改進一下後就變成了下面的樣子:
@Test public void testExpression99(){ long start = System.currentTimeMillis(); double d = Math.random(); double a = Math.random(); double b = Math.random(); double e = Math.random(); double p,x,y; for(int i=0;i<10000000;i++){ p = a*b/3*4*a; x = d*p; y = e*p; } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:11
經過運行結果咱們能夠看出來具體的優化效果。
同理,若是在某循環中須要執行一個耗時操做,而在循環體內,其執行結果老是惟一的,也應該提取到循環體外。
例以下面的代碼:
for(int i=0;i<100000;i++){ x[i] = Math.PI*Math.sin(y)*i; }
應該改進成下面的代碼:
//提取複雜,固定結果的業務邏輯處理到循環體外 double p = Math.PI*Math.sin(y); for(int i=0;i<100000;i++){ x[i] = p*i; }
數組複製是一項使用頻率很高的功能,JDK中提供了一個高效的API來實現它。
/** * @param src the source array. * @param srcPos starting position in the source array. * @param dest the destination array. * @param destPos starting position in the destination data. * @param length the number of array elements to be copied. * @exception IndexOutOfBoundsException if copying would cause * access of data outside array bounds. * @exception ArrayStoreException if an element in the <code>src</code> * array could not be stored into the <code>dest</code> array * because of a type mismatch. * @exception NullPointerException if either <code>src</code> or * <code>dest</code> is <code>null</code>. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
若是在應用程序中須要進行數組複製,應該使用這個函數,而不是本身實現。
下面來舉例:
@Test public void testArrayCopy(){ int size = 100000; int[] array = new int[size]; int[] arraydest = new int[size]; for(int i=0;i<array.length;i++){ array[i] = i; } long start = System.currentTimeMillis(); for (int k=0;k<1000;k++){ //進行復制 System.arraycopy(array,0,arraydest,0,size); } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:59
相對應地,若是在程序中,本身實現數組複製,其等價代碼以下:
@Test public void testArrayCopy99(){ int size = 100000; int[] array = new int[size]; int[] arraydest = new int[size]; for(int i=0;i<array.length;i++){ array[i] = i; } long start = System.currentTimeMillis(); for (int k=0;k<1000;k++){ for(int i=0;i<size;i++){ arraydest[i] = array[i]; } } long useTime = System.currentTimeMillis()-start; System.out.println("useTime:"+useTime); }
運行結果:
useTime:102
經過運行結果能夠看出效果。
由於System.arraycopy()函數是native函數,一般native函數的性能要優於普通函數。僅出於性能考慮,在程序開發時,應儘量調用native函數。
除NIO外,使用Java進行I/O操做有兩種基本方式;
一、使用基於InpuStream和OutputStream的方式;
二、使用Writer和Reader;
不管使用哪一種方式進行文件I/O,若是能合理地使用緩衝,就能有效地提升I/O的性能。
InputStream、OutputStream、Writer和Reader配套使用的緩衝組件。
以下圖:
使用緩衝組件對文件I/O進行包裝,能夠有效提高文件I/O的性能。
下面是一個直接使用InputStream和OutputStream進行文件讀寫的代碼:
@Test public void testOutAndInputStream(){ try { DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt")); long start = System.currentTimeMillis(); for(int i=0;i<10000;i++){ dataOutputStream.writeBytes(Objects.toString(i)+"\r\n"); } dataOutputStream.close(); long useTime = System.currentTimeMillis()-start; System.out.println("寫入數據--useTime:"+useTime); //開始讀取數據 long startInput = System.currentTimeMillis(); DataInputStream dataInputStream = new DataInputStream(new FileInputStream("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt")); while (dataInputStream.readLine() != null){ } dataInputStream.close(); long useTimeInput = System.currentTimeMillis()-startInput; System.out.println("讀取數據--useTimeInput:"+useTimeInput); }catch (Exception e){ e.printStackTrace(); } }
運行結果:
寫入數據--useTime:660
讀取數據--useTimeInput:274
使用緩衝的代碼以下:
@Test public void testBufferedStream(){ try { DataOutputStream dataOutputStream = new DataOutputStream( new BufferedOutputStream(new FileOutputStream("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt"))); long start = System.currentTimeMillis(); for(int i=0;i<10000;i++){ dataOutputStream.writeBytes(Objects.toString(i)+"\r\n"); } dataOutputStream.close(); long useTime = System.currentTimeMillis()-start; System.out.println("寫入數據--useTime:"+useTime); //開始讀取數據 long startInput = System.currentTimeMillis(); DataInputStream dataInputStream = new DataInputStream( new BufferedInputStream(new FileInputStream("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt"))); while (dataInputStream.readLine() != null){ } dataInputStream.close(); long useTimeInput = System.currentTimeMillis()-startInput; System.out.println("讀取數據--useTimeInput:"+useTimeInput); }catch (Exception e){ e.printStackTrace(); } }
運行結果:
寫入數據--useTime:22
讀取數據--useTimeInput:12
經過運行結果,咱們能很明顯的看出來使用緩衝的代碼,不管在讀取仍是寫入文件上,性能都有了數量級的提高。
使用Wirter和Reader也有相似的效果。
以下代碼:
@Test public void testWriterAndReader(){ try { long start = System.currentTimeMillis(); FileWriter fileWriter = new FileWriter("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt"); for (int i=0;i<100000;i++){ fileWriter.write(Objects.toString(i)+"\r\n"); } fileWriter.close(); long useTime = System.currentTimeMillis()-start; System.out.println("寫入數據--useTime:"+useTime); //開始讀取數據 long startReader = System.currentTimeMillis(); FileReader fileReader = new FileReader("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt"); while (fileReader.read() != -1){ } fileReader.close(); long useTimeInput = System.currentTimeMillis()-startReader; System.out.println("讀取數據--useTimeInput:"+useTimeInput); }catch (Exception e){ e.printStackTrace(); } }
運行結果:
寫入數據--useTime:221
讀取數據--useTimeInput:147
對應的使用緩衝的代碼:
@Test public void testBufferedWriterAndReader(){ try { long start = System.currentTimeMillis(); BufferedWriter fileWriter = new BufferedWriter( new FileWriter("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt")); for (int i=0;i<100000;i++){ fileWriter.write(Objects.toString(i)+"\r\n"); } fileWriter.close(); long useTime = System.currentTimeMillis()-start; System.out.println("寫入數據--useTime:"+useTime); //開始讀取數據 long startReader = System.currentTimeMillis(); BufferedReader fileReader = new BufferedReader( new FileReader("/IdeaProjects/client2/src/test/java/com/client2/cnblogtest/teststream.txt")); while (fileReader.read() != -1){ } fileReader.close(); long useTimeInput = System.currentTimeMillis()-startReader; System.out.println("讀取數據--useTimeInput:"+useTimeInput); }catch (Exception e){ e.printStackTrace(); } }
運行結果:
寫入數據--useTime:157
讀取數據--useTimeInput:59
經過運行結果能夠看出,使用了緩衝後,不管是FileReader仍是FileWriter的性能都有較爲明顯的提高。
在上面的例子中,因爲FileReader和FilerWriter的性能要優於直接使用FileInputStream和FileOutputStream因此循環次數增長了10倍。
後面會持續更新。。。
文章會同步到個人公衆號上面,歡迎關注。