通過Java代碼技巧改善性能
前言
程序的性能受到代碼質(zhì)量的直接影響。這次主要介紹一些代碼編寫的小技巧和慣例。雖然看起來有些是微不足道的編程技巧,卻可能為系統(tǒng)性能帶來成倍的提升,因此還是值得關注的。
慎用異常
在Java開發(fā)中,經(jīng)常使用try-catch進行錯誤捕獲,但是try-catch語句對系統(tǒng)性能而言是非常糟糕的。雖然一次try-catch中,無法察覺到她對性能帶來的損失,但是一旦try-catch語句被應用于循環(huán)或是遍歷體內(nèi),就會給系統(tǒng)性能帶來極大的傷害。
以下是一段將try-catch應用于循環(huán)體內(nèi)的示例代碼:
@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);
}
上面這段代碼運行結(jié)果是:
useTime:10
下面是一段將try-catch移到循環(huán)體外的代碼,那么性能就提升了將近一半。如下:
@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);
}
運行結(jié)果:
useTime:6
使用局部變量
調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。
下面是一段使用局部變量進行計算的代碼:
@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);
}
運行結(jié)果:
useTime:5
將局部變量替換為類的靜態(tài)變量:
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);
}
運行結(jié)果:
useTime:94
通過上面兩次的運行結(jié)果,可以看出來局部變量的訪問速度遠遠高于類成員變量。
位運算代替乘除法
在所有的運算中,位運算是最為高效的。因此,可以嘗試使用位運算代替部分算術運算,來提高系統(tǒng)的運行速度。最典型的就是對于整數(shù)的乘除運算優(yōu)化。
下面是一段使用算術運算的代碼:
@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);
}
運行結(jié)果:
useTime:1451
將循環(huán)體中的乘除運算改為等價的位運算,代碼如下:
@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);
}
運行結(jié)果:
useTime:10
上兩段代碼執(zhí)行了完全相同的功能,在每次循環(huán)中,都將整數(shù)乘以2,并除以2。但是運行結(jié)果耗時相差非常大,所以位運算的效率還是顯而易見的。
提取表達式
在軟件開發(fā)過程中,程序員很容易有意無意地讓代碼做一些“重復勞動”,在大部分情況下,由于計算機的高速運行,這些“重復勞動”并不會對性能構(gòu)成太大的威脅,但若希望將系統(tǒng)性能發(fā)揮到極致,提取這些“重復勞動”相當有意義。
比如以下代碼中進行了兩次算術計算:
@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);
}
運行結(jié)果:
useTime:21
仔細看能發(fā)現(xiàn),兩個計算表達式的后半部分完全相同,這也意味著在每次循環(huán)中,相同部分的表達式被重新計算了。
那么改進一下后就變成了下面的樣子:
@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);
}
運行結(jié)果:
useTime:11
通過運行結(jié)果我們可以看出來具體的優(yōu)化效果。
同理,如果在某循環(huán)中需要執(zhí)行一個耗時操作,而在循環(huán)體內(nèi),其執(zhí)行結(jié)果總是唯一的,也應該提取到循環(huán)體外。
例如下面的代碼:
for(int i=0;i<100000;i++){
x[i] = Math.PI*Math.sin(y)*i;
}
應該改進成下面的代碼:
//提取復雜,固定結(jié)果的業(yè)務邏輯處理到循環(huán)體外
double p = Math.PI*Math.sin(y);
for(int i=0;i<100000;i++){
x[i] = p*i;
}
使用arrayCopy()
數(shù)組復制是一項使用頻率很高的功能,JDK中提供了一個高效的API來實現(xiàn)它。
/**
* @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);
如果在應用程序中需要進行數(shù)組復制,應該使用這個函數(shù),而不是自己實現(xiàn)。
下面來舉例:
@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);
}
運行結(jié)果:
useTime:59
相對應地,如果在程序中,自己實現(xiàn)數(shù)組復制,其等價代碼如下:
@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);
}
運行結(jié)果:
useTime:102
通過運行結(jié)果可以看出效果。
因為System.arraycopy()函數(shù)是native函數(shù),通常native函數(shù)的性能要優(yōu)于普通函數(shù)。僅出于性能考慮,在程序開發(fā)時,應盡可能調(diào)用native函數(shù)。
相關文章
Java調(diào)用SQL腳本執(zhí)行常用的方法示例
這篇文章主要給大家介紹了關于Java調(diào)用SQL腳本執(zhí)行常用的方法的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧。2018-04-04
MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler
這篇文章主要為大家介紹了MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
解讀Spring定義Bean的兩種方式:<bean>和@Bean
這篇文章主要介紹了Spring定義Bean的兩種方式:<bean>和@Bean,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
windows 部署JAVA環(huán)境安裝iDea的詳細步驟
這篇文章主要介紹了windows 部署JAVA環(huán)境安裝iDea的詳細步驟,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08

