前言
程式的性能受到代碼品質的直接影響。這次主要介紹一些代碼編寫的小技巧和慣例。雖然看起來有些是微不足道的程式設計技巧,卻可能為系統性能帶來成倍的提升,是以還是值得關注的。
慎用異常
在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倍。