Java程序性能優(yōu)化—十年碼農(nóng)總結(jié)的編程小技巧

程序的性能受代碼質(zhì)量的直接影響。在本文中,主要介紹一些代碼編寫的小技巧和慣例,這些技巧有助于在代碼級別上提升系統(tǒng)性能。

1、慎用異常

在Java軟件開發(fā)中,經(jīng)常使用 try-catch 進行錯誤捕獲,但是,try-catch 語句對系統(tǒng)性能而言是非常糟糕的。雖然在一次 try-catch中,無法察覺到它對性能帶來的損失,但是,一旦try-catch被應(yīng)用于循環(huán)之中,就會給系統(tǒng)性能帶來極大的傷害。

以下是一段將try-catch應(yīng)用于for循環(huán)內(nèi)的示例


這段代碼我運行時間是 27211 ms。如果將try-catch移到循環(huán)體外,那么就能提升系統(tǒng)性能,如下代碼


運行耗時 15647 ms??梢妕yr-catch對系統(tǒng)性能的影響。

2、使用局部環(huán)境

調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。

下面是一段測試用例

// 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);

}

運行結(jié)果很明顯,使用靜態(tài)變量耗時15677ms,使用局部變量耗時13509ms。由此可見,局部變量的訪問速度高于類的成員變量。

3、位運算代替乘除法

在所有的運算中,位運算是最為高效的。因此,可以嘗試使用位運算代替部分算術(shù)運算,來提高系統(tǒng)的運行速度。

比如在HashMap的源碼中使用了位運算

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

static final int MAXIMUM_CAPACITY = 1 << 30;

對于整數(shù)的乘除運算優(yōu)化

a*=2

a/=2

用位運算可以寫為

a<<=1a>>=1

4、替換switch

關(guān)鍵字 switch 語句用于多條件判斷, switch 語句的功能類似于 if-else 語句,兩者性能也差不多。因此,不能說 switch 語句會降低系統(tǒng)的性能。但是,在絕大部分情況下,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,實現(xiàn)相同的程序功能,性能就能有很大的提升空間。

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];

}

}

以上代碼使用全新的思路,使用一個連續(xù)的數(shù)組代替了 switch 語句。因為對數(shù)據(jù)的隨機訪問是非??斓模辽俸糜?switch 的分支判斷。通過實驗,使用switch的語句耗時17860ms,使用數(shù)組的實現(xiàn)只耗時12590ms,提升了5s多。在軟件開發(fā)中,換一種思路可能會取得更好的效果,比如使用數(shù)組替代switch語句就是就是一個很好的例子。在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流裙。交流學(xué)習(xí)裙號:821169538,里面會分享一些資深架構(gòu)師錄制的視頻錄像

5、一維數(shù)組代替二維數(shù)組

由于數(shù)組的隨機訪問的性能非常好,許多JDK類庫,如ArrayList、Vector等都是使用了數(shù)組作為其數(shù)組實現(xiàn)。但是,作為軟件開發(fā)人員也必須知道,一位數(shù)組和二維數(shù)組的訪問速度是不一樣的。一位數(shù)組的訪問速度要優(yōu)于二維數(shù)組。因此,在性能敏感的系統(tǒng)中要使用二維數(shù)組的,可以嘗試通過可靠地算法,將二維數(shù)組轉(zhuǎn)為一維數(shù)組再進行處理,以提高系統(tǒng)的響應(yīng)速度。

6、提取表達式

在軟件開發(fā)過程中,程序員很容易有意無意讓代碼做一些“重復(fù)勞動”,在大部分情況下,由于計算機的告訴運行,這些“重復(fù)勞動”并不會對性能構(gòu)成太大的威脅,但若將系統(tǒng)性能發(fā)揮到極致,提取這些“重復(fù)勞動”相當(dāng)有意義。

來看下面的測試用例:

@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()方法提取出來,優(yōu)化后的代碼如下:

@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左右,可見,提取重復(fù)的操作是相當(dāng)有意義的。

7、展開循環(huán)

與前面所介紹的優(yōu)化技巧略有不同,筆者認為展開循環(huán)是一種在極端情況下使用的優(yōu)化手段,因為展開循環(huán)很可能會影響代碼的可讀性和可維護性,而這兩者對軟件系統(tǒng)來說也是極為重要的。但是,當(dāng)性能問題成為系統(tǒng)主要矛盾時,展開循環(huán)絕對是一種值得嘗試的技術(shù)。

8、布爾運算代替位運算

雖然位運算的速度遠遠高于算術(shù)運算,但是在條件判斷時,使用位運算替代布爾運算卻是非常錯誤的選擇。

在條件判斷時,Java會對布爾運算做相當(dāng)充分的優(yōu)化。假設(shè)有表達式 a,b,c 進行布爾運算“a&&b&&c” ,根據(jù)邏輯與的特點,只要在整個布爾表達式中有一項返回false,整個表達式就返回false,因此,當(dāng)表達式a為false時,該表達式將立即返回 false ,而不會再去計算表達式b 和c。同理,當(dāng)計算表達式為“a||b||c”時,也是一樣。

若使用位運算(按位與”&“、按位或”|“)代替邏輯與和邏輯或,雖然位運算本身沒有性能問題,但是位運算總是要將所有的子表達式全部計算完成后,再給出最終結(jié)果。因此,從這個角度來說,使用位運算替代布爾運算會使系統(tǒng)進行很多無效計算。

9、使用arrayCopy()

數(shù)組復(fù)制是一項使用頻率很高的功能,JDK中提供了一個高效的API來實現(xiàn)它:

如果在應(yīng)用程序需要進行數(shù)組復(fù)制,應(yīng)該使用這個函數(shù),而不是自己實現(xiàn)。

方法代碼:

public static native void arraycopy(Object src, int srcPos,

Object dest, int destPos, int length);

它的用法是將源數(shù)組 src 從索引 srcPos 處復(fù)制到目標數(shù)組 dest 的 索引destPos處,復(fù)制的長度為 length。

System.arraycopy() 方法是 native 方法,通常 native 方法的性能要優(yōu)于普通的方法。僅出于性能考慮,在軟件開發(fā)中,盡可能調(diào)用 native 方法。

10、使用Buffer進行I/O流操作

除NIO外,使用 Java 進行 I/O操作有兩種基本方法:

使用基于InputStream 和 OutputStream 的方式;(字節(jié)流)

使用 Writer 和 Reader。(字符流)

無論使用哪種方式進行文件 I/O,如果能合理地使用緩沖,就能有效的提高I/O的性能。

11、使用clone()代替new

在Java中新建對象實例最常用的方法是使用 new 關(guān)鍵字。JDK對 new 的支持非常好,使用 new 關(guān)鍵字創(chuàng)建輕量級對象時,速度非???。但是,對于重量級對象,由于對象在構(gòu)造函數(shù)中可能會進行一些復(fù)雜且耗時的操作,因此,構(gòu)造函數(shù)的執(zhí)行時間可能會比較長。導(dǎo)致系統(tǒng)短期內(nèi)無法獲得大量的實例。為了解決這個問題,可以使用Object.clone() 方法。

Object.clone() 方法可以繞過構(gòu)造函數(shù),快速復(fù)制一個對象實例。但是,在默認情況下,clone()方法生成的實例只是原對象的淺拷貝。

這里不得不提Java只有值傳遞了,關(guān)于這點,我的理解是基本數(shù)據(jù)類型引用的是值,普通對象引用的也是值,不過這個普通對象引用的值其實是一個對象的地址。代碼示例:在此我向大家推薦一個架構(gòu)學(xué)習(xí)交流裙。交流學(xué)習(xí)裙號:821169538,里面會分享一些資深架構(gòu)師錄制的視頻錄像

int i = 0; int j = i; //i的值是0

User user1 = new User();

User user2 = user1; //user1值是new User()的內(nèi)存地址

如果需要深拷貝,則需要重新實現(xiàn) clone() 方法。下面看一下ArrayList實現(xiàn)的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數(shù)組讓新的ArrayList去引用。使克隆后的ArrayList對象與原對象持有不同的引用,實現(xiàn)了深拷貝。

12、靜態(tài)方法替代實例方法

使用 static 關(guān)鍵字描述的方法為靜態(tài)方法。在Java中,由于實例方法需要維護一張類似虛函數(shù)表的結(jié)構(gòu),以實現(xiàn)對多態(tài)的支持。與靜態(tài)方法相比,實例方法的調(diào)用需要更多的資源。因此,對于一些常用的工具類方法,沒有對其進行重載的必要,那么將它們聲明為 static,便可以加速方法的調(diào)用。同時,調(diào)用 static 方法不需要生成類的實例。比調(diào)用實例方法更為方便、易用。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,697評論 18 399
  • 【程序1】 題目:古典問題:有一對兔子,從出生后第3個月起每個月都生一對兔子,小兔子長到第三個月后每個月又生一對兔...
    葉總韓閱讀 5,227評論 0 41
  • 工作&學(xué)習(xí) 好多臨時事項,好在最重要的報告已完結(jié)。 Python 和mysql的學(xué)習(xí)本周有停滯,下周那實例上手練。...
    聶Lydia閱讀 107評論 0 0
  • 一夜未眠 收拾好幾個月的緊張心情 在破曉時分西裝革履 一路鳴炮的迎親車隊 在歡樂感動的氣氛中 我女朋友 站在舞臺中...
    流浪山狼閱讀 205評論 0 1
  • UIPickView和UIDatePicker 1.UIPickView什么時候用?? 通常在注冊模塊,當(dāng)用戶需要...
    SoManyDumb閱讀 442評論 0 0

友情鏈接更多精彩內(nèi)容