天天看點

看完這些 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移到循環體外的代碼,那麼性能就提升了将近一半。如下:

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

}

運作結果:

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

将局部變量替換為類的靜态變量:

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 = dab/34a;

y = eab/34a;

}

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 = ab/34a;

x = dp;

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.PIMath.sin(y); for(int

i=0;i<100000;i++){

x[i] = pi; }

使用arrayCopy()

數組複制是一項使用頻率很高的功能,JDK中提供了一個高效的API來實作它。

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配套使用的緩沖元件。

如下圖:

看完這些 Java 代碼優秀案例,一定對你有提升

使用緩沖元件對檔案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倍。