Java性能調優
寫Java也有n年了,現在還是有不少的壞的代碼習慣,也通過學習別人的代碼學到了不少好的習慣。這篇文章主要是整理的資料。留給自己做個警戒,提示以后寫代碼的時候注意!在文章的后面,會提供整理的原材料下載。
一、類和對象使用技巧
1、盡量少用new生成新對象
用new創建類的實例時,構造雨數鏈中所有構造函數都會被自動調用,操作速度較慢。在某些時候可復用現有對象。比如在進行大量St rillg操作時,可用StringBuffer婁代替String類,以避免生成大量的對象。2、使用clone()方法生成新對象
如果一個對象實現r C10neable接口,就可以調用它的clone()方法。clone()方法不會淚用任何類構造函數,比使用new方法創建實例速度要快。3、盡量使用局部變量(棧變量)
調用方法時傳遞的參數及調用中創建的臨時變量保存在棧(Stack)中,速度較快。其他變量,如靜態變量、實例變量都在堆(HeaP)中創建,速度較慢。訪問靜態變量和實例變量將會比訪問局部變量多耗費 2-3 個時鐘周期。</span>如果一個變量需要經常訪問,那么你就需要考慮這個變量的作用域了。static? local?還是實例變量?
例子:
public class usv {
void getsum (int[] values) {
for (int i=0; i < value.length; i++) {
_sum += value[i]; // violation.
}
}
void getsum2 (int[] values) {
for (int i=0; i < value.length; i++) {
_staticsum += value[i];
}
}
private int _sum;
private static int _staticsum;
}
更正:如果可能,請使用局部變量作為你經常訪問的變量。 你可以按下面的方法來修改 getsum()方法:
void getsum (int[] values) {
int sum = _sum; // temporary local variable.
for (int i=0; i < value.length; i++) {
sum += value[i];
}
_sum = sum;
4、減少方法調用
面向對象設計的一個基本準則是通過方法間接訪問對象的屬性,但方法調用會占用·些開銷訪問。可以避免在同一個類中通過訓州函數或方法(get或set)來設置或調用該對象的災例變量,這比直接訪問變量要慢。為了減少方法調用,可以將方法中的代碼復制到調用方法的地方,比如大量的循環中,這樣可以節省方法調用的開銷。但帶來性能提升的同時會犧牲代碼的可讀性,可根據實際需要平衡兩者關系。5、使用final類和final、static、private方法
帶有final修飾符的類是不可派生的。如果指定一個類為finaI,則該類所有的方法都是final。JAVA編障器會尋找機會內聯(inIine)所有的final方法。此舉能夠提升程序性能。使用final、static、private的方法是不能被覆蓋的,JAVA不需要在稃序運行的時候動態關聯實現方法,從而節省了運行時間。6、讓訪問實例內變量的 getter/setter 方法變成”final”
簡單的 getter/setter 方法應該被置成 final,這會告訴編譯器,這個方法不會被重載,所以,可以變成”inlined”
例子:
class maf {
public void setsize (int size) {
_size = size;
}
private int _size;
} 更正: class daf_fixed {
final public void setsize (int size) {
_size = size;
}
private int _size;
} 7、避免不需要的 instanceof 操作
如果左邊的對象的靜態類型等于右邊的,instanceof 表達式返回永遠為 true。
例子:
public class uiso {
public uiso () {}
}
class dog extends uiso {
void method (dog dog, uiso u) {
dog d = dog;
if (d instanceof uiso) // always true.
system.out.println("dog is a uiso");
uiso uiso = u;
if (uiso instanceof object) // always true.
system.out.println("uiso is an object");
}
}
更正:刪掉不需要的 instanceof 操作。
class dog extends uiso {
void method () {
dog d;
system.out.println ("dog is an uiso");
system.out.println ("uiso is an uiso");
}
} 8、避免不需要的造型操作
所有的類都是直接或者間接繼承自 object。同樣,所有的子類也都隱含的“等于”其父類。那么,由子類造型至父類的操作就是不必要的了。
例子:
class unc {
string _id = "unc";
}
class dog extends unc {
void method () {
dog dog = new dog ();
unc animal = (unc)dog; // not necessary.
object o = (object)dog; // not necessary.
}
} 更正:
class dog extends unc {
void method () {
dog dog = new dog();
unc animal = dog;
object o = dog;
}
} 二、JavaI/O技巧
I/O性能常常是應用程序性能瓶頸,優化I/O性能就顯得極為系要。在進行I/0操作時,匿遵循以下原則:盡可能少的訪問磁盤;盡可能少的I方問底層的操作系統;琳可能少的方法調用;盡r叮能少處理個別的處理字節和字符。基于以上原則,可以通過以下技巧提高I/O速度:1、使州緩沖提高I/O性能。
常用的實現方法有以下2種:使用用于字符的BufferedReader和用于寧節的BufferedlnputStream類,或者使用塊讀取方法次讀取一大塊數據。
2、lnputStream比Reader高效,OutputStream比Writer高效。
當使用Unicode字符串時,Write類的開銷比較大。因為它要實Uoicode到字節(byte)的轉換。兇此,如果可能的話,在使_}}j Write類之前就實現轉換或用OutputStream類代替Writer婁來使用。
3、在適當的時候用byte替代char。
1個char用2個字節保存字符,而byte只需要1個,因此用byte保存字符消耗的內存和需要執行的機器指令更少。更重要的是,用byte避免了進行Unicode轉換。因此,如果町能的話,應盡量使用byte替代ch ar。
4、有緩沖的塊操作10要比緩沖的流字符IO快。
對于字符IO,雖然緩沖流避免了每次讀取字符時的系統調用開銷,但仍需要一次或多次方法調用。帶緩沖的塊10比緩沖流IO快2到4倍,比無緩沖的IO快4到40倍。
5、序列化時,使用原子類型。
當序列化一個類或對象時,對干那些原子類型(atomic)或可以重建的元素,要標識為transient類型,這樣就不用每一次都進行序列化。如果這砦序列化的對象要在網絡上傳輸,這一小小的改變對性能會有很大的提高。
6、在finally 塊中關閉stream
例子:
import java.io.*;
public class cs {
public static void main (string args[]) {
cs cs = new cs ();
cs.method ();
}
public void method () {
try {
fileinputstream fis = new fileinputstream ("cs.java");
int count = 0;
while (fis.read () != -1)
count++;
system.out.println (count);
fis.close ();
} catch (filenotfoundexception e1) {
} catch (ioexception e2) {
}
}
} 更正:
在最后一個 catch 后添加一個 finally 塊
三、異常(Exceptions)使用技巧
JAVA語言中提供了try/catch來開發方便用戶捕捉異常,進行異常的處理。但是如果使用不當,也會給JAvA程序的性能帶來影響。因此,要注意以下兒點。慎用異常,異常對性能不利。拋出異常首先要創建一個新的對象。Throwable接口的構造函數調用名為fillInStackTrace()的本地(Native)方法filllnStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,VM就必須調整調用堆棧。1、避免使用異常來控制程序流程
如果可以用if、while等邏輯語句來處理,那么就盡可能的不用try/catch語句2、盡可能重用異常
在必須要進行異常的處理時,要盡可能的重用已經存在的異常對象。因為在異常的處理中,生成個異常對象要消耗掉大部分的時間。3、將try/catch 塊移出循環
把 try/catch塊放入循環體內,會極大的影響性能,如果編譯 jit 被關閉或者你所使用的是一個不帶 jit 的jvm,性能會將下降 21%之多!
例子:
import java.io.fileinputstream;
public class try {
void method (fileinputstream fis) {
for (int i = 0; i < size; i++) {
try { // violation
_sum += fis.read();
} catch (exception e) {}
}
}
private int _sum;
} 更正:將 try/catch塊移出循環 void method (fileinputstream fis) {
try {
for (int i = 0; i < size; i++) { _sum += fis.read();
}
} catch (exception e) {}
}
四、線程使用技巧
1、在使用大量線程(Threading)的場合使用線程池管理
生成和啟動新線程是個相對較慢的過程,生成人量新線程會嚴重影響應J_}j程序性能。通過使用線程池,由線程池管理器(thread pool manager)來生成新線程或分配現有線程,柏省生成線程的叫間。
2、防止過多的同步
不必要的同步常常會造成程序性能的下降,調用同步方法比調用非同步方法要花費更多的時間。如果程序是單線程,則沒有必要使用同步。3、同步方法而不要同步整個代碼段
對某個方法或函數進行同步比對整個代碼段進行同步的性能要好。4、在追求速度的場合,用ArrayList和HashMap代替Vector和Hashtable
Vector和Hashtable實現了同步以提高線程安全性,但速度較沒有實現同步的ArrayList和Ha shMap要慢,可以根據需要選擇使用的類。5、使用notify()而不是notifyAll()
使用哪個方法要取決于程序的沒計,但應盡可能使用notify(),因為notify()只喚醒等待指定對象的線程,比喚醒所有等待線稃的notifyAll0速度更快。6、不要在循環中調用 synchronized(同步)方法
方法的同步需要消耗相當大的資料,在一個循環中調用它絕對不是一個好主意。例子:
import java.util.vector;
public class syn {
public synchronized void method (object o) {
}
private void test () {
for (int i = 0; i < vector.size(); i++) {
method (vector.elementat(i)); // violation
}
}
private vector vector = new vector (5, 5);
} 更正:不要在循環體中調用同步方法,如果必須同步的話,推薦以下方式:
import java.util.vector;
public class syn {
public void method (object o) {
}
private void test () {
synchronized{//在一個同步塊中執行非同步方法
for (int i = 0; i < vector.size(); i++) {
method (vector.elementat(i));
}
}
}
private vector vector = new vector (5, 5);
}
五、其它常用技巧
1、使用移位操作替代乘除法操作可以極大地提高性能
例子:
public class sdiv {
public static final int num = 16;
public void calculate(int a) {
int div = a / 4; // should be replaced with "a >> 2".
int div2 = a / 8; // should be replaced with "a >> 3".
int temp = a / 3;
int mul = a * 4; // should be replaced with "a << 2".
int mul2 = 8 * a; // should be replaced with "a << 3".
int temp2 = a * 3;
}
} public class sdiv {
public static final int num = 16;
public void calculate(int a) {
int div = a >> 2;
int div2 = a >> 3;
int temp = a / 3; // 不能轉換成位移操作
int mul = a << 2;
int mul2 = a << 3;
int temp = a * 3; // 不能轉換
}
} 2、對Vector中最后位置的添加、刪除操作要遠遠快于塒第一個元素的添加、刪除操作
3、當復制數組時,使用System.arraycop()方法
public class irb
{
void method () {
int[] array1 = new int [100];
for (int i = 0; i < array1.length; i++) {
array1 [i] = i;
}
int[] array2 = new int [100];
for (int i = 0; i < array2.length; i++) {
array2 [i] = array1 [i]; // violation
}
}
} 更正:
public class irb
{
void method () {
int[] array1 = new int [100];
for (int i = 0; i < array1.length; i++) {
array1 [i] = i;
}
int[] array2 = new int [100];
system.arraycopy(array1, 0, array2, 0, 100);
}
} 4、使用復合賦值運算符
a=a+b和a+b住編譯時會產生不同JAVA字奇碼,后者回快于前者。岡此,使用+=、一、+=、/=等復臺賦值運算符會使運算速度稍有提升。5、用int而不用其它基本類型
對int類犁的操作通常比其它基本類型要快,因此盡量使用int類型。6、在進行數據庫連接和網絡連接時使用連接池
這類連接往往會耗費大量時間,應盡量避免。可以使用連接池技術,復用現有連接。7、用壓縮加快網絡傳輸速度一種常用方法是把相關文件打包到一個jar文件中。
用一個Jar文件發送多個文件還叫以避免每個文件打開和關閉網絡連接所造成的開銷。
8、在數據庫應用程序中使用批處理功能
可以利用Statement類的addBatch()氟l exexuteBatch法成批地提交sql語句,以節省網絡傳輸開銷。在執行大量相似語句時,可以使用PreParedState—類,它可以一次性編譯語句并多次執行,用參數最后執行的sql語句。
9、消除循環體中不必要的代碼
這似乎是每個程序員都知道的基本原則,沒有必出,但很多人往往忽略一些細節。如下列代碼:Vector aVector= ...; for(int i=0;i<aVector size();i++)( System out printlll(aVector elementAt(i)toStringO); }
這段代碼中沒循環一次就要調用aVector.size()方法,aVector的長度不變的話,可以改為一下代碼:
Vector aVector= ...: int length=aVector size(); for(int i=0;i<length;i++)f System out println(aVector elememAt(i)toStringO); )
這樣消除了每次調用aVector.size()方法的開銷。
10、為'vectors' 和 'hashtables'定義初始大小
例子:
import java.util.vector;
public class dic {
public void addobjects (object[] o) {
// if length > 10, vector needs to expand
for (int i = 0; i< o.length;i++) {
v.add(o); // capacity before it can add more elements.
}
}
public vector v = new vector(); // no initialcapacity.
} 更正: 自己設定初始大小。 public vector v = new vector(20);
public hashtable hash = new hashtable(10); 11、如果只是查找單個字符的話,用charat()代替startswith()
例子:
public class pcts {
private void method(string s) {
if (s.startswith("a")) { // violation
// ...
}
}
} 更正 :將'startswith()' 替換成'charat()'. public class pcts {
private void method(string s) {
if ('a' == s.charat(0)) {
// ...
}
}
} 12、在字符串相加的時候,使用 ' ' 代替 " ",如果該字符串只有一個字符的話
例子:
public class str {
public void method(string s) {
string string = s + "d" // violation.
string = "abc" + "d" // violation.
}
} 更正: 將一個字符的字符串替換成' '
public class str {
public void method(string s) {
string string = s + 'd'
string = "abc" + 'd'
}
} 13、對于 boolean 值,避免不必要的等式判斷
將一個 boolean 值與一個 true 比較是一個恒等操作(直接返回該 boolean 變量的值). 移走對于boolean 的不必要操作至少會帶來 2 個好處:
1)代碼執行的更快 (生成的字節碼少了 5 個字節);
2)代碼也會更加干凈 。
例子:
public class ueq {
boolean method (string string) {
return string.endswith ("a") == true; // violation
}
} 更正: class ueq_fixed {
boolean method (string string) {
return string.endswith ("a");
}
} 14、對于常量字符串,用'string' 代替 'stringbuffer'
常量字符串并不需要動態改變長度。例子:
public class usc {
string method () {
stringbuffer s = new stringbuffer ("hello");
string t = s + "world!";
return t;
}
} 更正: 把 stringbuffer 換成 string,如果確定這個 string 不會再變的話,這將會減少運行開銷提高性能。
15、用'stringtokenizer' 代替 'indexof()' 和'substring()'
例子:
public class ust {
void parsestring(string string) {
int index = 0;
while ((index = string.indexof(".", index)) != -1) {
system.out.println (string.substring(index, string.length()));
}
}
} 16、十七、使用條件操作符替代"if (cond) else " 結構
條件操作符更加的簡捷
例子:
public class if {
public int method(boolean isdone) {
if (isdone) {
return 0;
} else {
return 10;
}
void method2(boolean istrue) {
if (istrue) {
_value = 0;
} else {
_value = 1; }
}
} 更正:
public class if {
public int method(boolean isdone) {
return (isdone ? 0 : 10);
}
void method(boolean istrue) {
_value = (istrue ? 0 : 1); // comp }
private int _value = 0;
} 17、不要在循環體中實例化變量
在循環體中實例化臨時變量將會增加內存消耗例子:
import java.util.vector;
public class loop {
void method (vector v) {
for (int i=0;i < v.size();i++) {
object o = new object();
o = v.elementat(i);
}
}
} 更正:
在循環體外定義變量,并反復使用
import java.util.vector;
public class loop {
void method (vector v) {
object o;
for (int i=0;i<v.size();i++) {
o = v.elementat(i);
}
}
} 18、確定 stringbuffer的容量
stringbuffer 的構造器會創建一個默認大小(通常是 16)的字符數組。在使用中,如果超出這個大小,就會重新分配內存,創建一個更大的數組,并將原先的數組復制過來,再丟棄舊的數組。在大多數情況下,你可以在創建 stringbuffer 的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。例子:
public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(); // violation
buffer.append ("hello");
}
} 更正:為 stringbuffer 提供寢大小。 public class rsbc {
void method () {
stringbuffer buffer = new stringbuffer(max);
buffer.append ("hello");
}
private final int max = 100;
} 19、不要總是使用取反操作符(!)
取反操作符(!)降低程序的可讀性,所以不要總是使用。
例子:
public class dun {
boolean method (boolean a, boolean b) {
if (!a)
return !a;
else
return !b;
}
} 更正:如果可能不要使用取反操作符(!)
20、與一個接口 進行instanceof 操作
基于接口的設計通常是件好事,因為它允許有不同的實現,而又保持靈活。只要可能,對一個對象進行 instanceof 操作,以判斷它是否某一接口要比是否某一個類要快。
例子:
public class insof {
private void method (object o) {
if (o instanceof interfacebase) { } // better
if (o instanceof classbase) { } // worse.
}
}
class classbase {}
interface interfacebase {}