有助於改善性能的Java代碼技巧

前言

程序的性能受到代碼質量的直接影響。此次主要介紹一些代碼編寫的小技巧和慣例。雖然看起來有些是微不足道的編程技巧,卻可能爲系統性能帶來成倍的提高,所以仍是值得關注的。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;
}

使用arrayCopy()

數組複製是一項使用頻率很高的功能,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函數。

使用Buffer進行I/O操做

除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倍。

 

 

 

 

後面會持續更新。。。

 

文章會同步到個人公衆號上面,歡迎關注。

相關文章
相關標籤/搜索