在Java程序中,性能問題的原因大多不是Java語言,而是程序本身。養成良好的編碼習慣非常重要,比如正確熟練地使用java.lang.String和java.util.Vector,可以顯著提高程序的性能。我們來具體分析壹下這個問題。
1.嘗試指定帶有final修飾符的類是不可派生的。在Java核心API中,有很多應用final的例子,比如java.lang.String為String類指定final可以防止人們重寫length()方法。此外,如果某個類被指定為final,則該類的所有方法都是final。Java編譯器會尋找機會內聯所有的final方法(這與具體的編譯器實現有關)。這可以平均提高50%的性能。
。
2.盡可能重用對象。尤其是在使用String對象時,發生字符串連接時,應該使用StringBuffer代替。因為系統不僅需要時間來生成對象,將來還可能需要時間來收集和處理這些對象。因此,生成過多的對象會對程序的性能產生很大的影響。
3.盡量使用局部變量,調用方法時傳遞的參數和調用中創建的臨時變量都保存在堆棧中,這樣更快。其他變量,比如靜態變量和實例變量,都是在堆裏創建的,比較慢。此外,根據特定的編譯器/JVM,局部變量可以進壹步優化。請參見盡可能使用堆棧變量。
4.不要重復初始化變量。默認情況下,當調用類的構造函數時,
Java將變量初始化為某些值:所有對象設置為null,整型變量(byte,short,int,long)設置為0,float和double變量設置為0.0,邏輯值設置為false。當壹個類派生自另壹個類時,這壹點尤其重要,因為當用new關鍵字創建壹個對象時,構造函數鏈中的所有構造函數都會被自動調用。
5.在JAVA+ORACLE應用系統的開發中,嵌入在JAVA中的SQL語句要盡量大寫,以減輕ORACLE解析器的解析負擔。
6.在Java編程的過程中,要小心數據庫連接和I/O流操作。用完後甚至關閉它釋放資源。因為對這些大型對象的操作會造成系統很大的開銷,稍有不慎就會導致嚴重的後果。
7.由於JVM有自己的GC機制,不需要程序開發者過多考慮,壹定程度上減輕了開發者的負擔,但也遺漏了隱患。過多的創建對象會消耗大量的系統內存,嚴重時會導致內存泄漏。因此,保證過期物品的及時回收意義重大。JVM回收垃圾的條件是:對象不再被引用;但是JVM的GC並不是很機智,即使對象滿足垃圾回收的條件,也不壹定能馬上回收。所以建議我們在使用完對象後,手動將對象設置為null。
8.在使用同步機制時,應盡量使用方法同步,而不是代碼塊同步。
9、盡量減少變量的重復計算。
例如:for(int I = 0;我& ltlist.sizei ++) {
…
}
應替換為:
for(int i = 0,int len = list . size();我& ltleni ++) {
…
}
10,盡量采用懶加載的策略,即在需要的時候開始創建。
比如:String str = " aaa
if(i == 1) {
list . add(str);
}
應替換為:
if(i == 1) {
String str = " aaa
list . add(str);
}
11.小心使用異常。
異常不利於性能。若要引發異常,必須首先創建壹個新的對象。Throwable接口的構造函數調用壹個名為fillInStackTrace()的本機方法,fillInStackTrace()方法檢查堆棧並收集調用跟蹤信息。每當拋出異常時,VM必須調整調用堆棧,因為在處理過程中創建了壹個新對象。異常只能用於錯誤處理,不應用於控制程序流。
12,不要在循環中使用:
嘗試{
} catch() {
}
應該放在最外層。
13和StringBuffer的使用:
StringBuffer表示壹個可變的、可寫的字符串。
有三種構造方法:
string buffer();//默認分配是16個字符。
string buffer(int size);//分配大小字符的空間
StringBuffer(字符串str);//分配16個字符+str.length()個字符空間。
可以通過StringBuffer的構造函數來設置它的初始化容量,這樣可以明顯提高性能。這裏提到的構造函數是StringBuffer(int
Length),length參數指示當前StringBuffer可以容納的字符數。也可以使用ensureCapacity(int
minimumcapacity)方法在創建StringBuffer對象後設置其容量。首先,讓我們看看StringBuffer的默認行為,然後找到壹個更好的方法來提高性能。
StringBuffer在內部維護壹個字符數組。當使用默認構造函數創建StringBuffer對象時,StringBuffer的容量被初始化為16個字符,因為未設置初始化字符長度,這意味著默認容量為16個字符。當StringBuffer達到最大容量時,它會將其容量增加到當前容量的兩倍加2,即(2*舊值+2)。如果使用默認值,則在初始化後,向其中添加字符。當您添加到16字符時,它會將容量增加到34(2*16+2),當您添加到34個字符時,它會將容量增加到70(2*34+2)。無論發生什麽情況,只要StringBuffer達到其最大容量,它就必須創建壹個新的字符數組,然後再次復制新舊字符——這太昂貴了。因此,始終為StringBuffer設置壹個合理的初始化容量值並沒有錯,這樣會帶來立竿見影的性能增益。
StringBuffer初始化過程中調整的作用由此可見壹斑。因此,用適當的容量值初始化StringBuffer始終是最好的建議。
14,合理使用Java類java.util.Vector
簡單地說,Vector是java.lang.Object實例的數組。Vector類似於數組,它的元素可以通過整數形式的索引來訪問。但是,在創建Vector類型的對象後,對象的大小可以根據元素的添加或刪除而擴大或縮小。考慮以下向向量添加元素的示例:
Object obj = new Object();
向量v =新向量(100000);
for(int I = 0;
我& lt100000;I++) { v.add(0,obj);}
除非每次都有絕對充分的理由在Vector前面插入壹個新元素,否則上面的代碼對性能沒有好處。在默認的構造函數中,Vector的初始存儲容量是10個元素。如果添加新元素時存儲容量不足,那麽以後每次存儲容量都會增加壹倍。Vector類就像StringBuffer類壹樣。每次擴展存儲容量時,所有現有元素都會被復制到新的存儲空間中。以下代碼片段比前壹個示例快幾個數量級:
Object obj = new Object();
向量v =新向量(100000);
for(int I = 0;我& lt100000;i++){ v . add(obj);}
同樣的規則也適用於Vector類的remove()方法。因為Vector中的元素之間不能有“間隙”,所以刪除除最後壹個元素之外的任何其他元素都會導致被刪除元素之後的元素向前移動。換句話說,從向量中刪除最後壹個元素的“開銷”比刪除第壹個元素的“開銷”低幾倍。
假設我們想刪除先前向量中的所有元素,我們可以使用以下代碼:
for(int I = 0;我& lt100000;I++)
{
v . remove(0);
}
但是,與下面的代碼相比,前面的代碼要慢幾個數量級:
for(int I = 0;我& lt100000;I++)
{
v . remove(v . size()-1);
}
從Vector類型的對象V中刪除所有元素的最佳方法是:
v . remove allements();
假設Vector類型的對象V包含字符串“Hello”。考慮下面的代碼,它從Vector中刪除了“Hello”字符串:
String s = " Hello
int I = v . index of(s);
如果(我!=-1)v . remove;
這段代碼看起來沒什麽問題,但是對性能也不好。在這段代碼中,indexOf()方法按順序在V中搜索字符串“Hello ”, remove(s)方法也按相同的順序搜索。改進後的版本是:
String s = " Hello
int I = v . index of(s);
如果(我!=-1)v . remove(I);
在這個版本中,我們在remove()方法中直接給出了要刪除的元素的確切索引位置,從而避免了第二次搜索。更好的版本是:
String s = " Hello五.移除;
最後,讓我們看壹個關於Vector類的代碼片段:
for(int I = 0;i++;我& lt五.長度)
如果v包含100,000個元素,這個代碼片段將調用v.size()方法100,000次。雖然size方法是壹個簡單的方法,但它仍然需要方法調用的開銷,至少JVM需要為它配置和清除堆棧環境。這裏,for循環內部的代碼不會以任何方式修改Vector類型對象V的大小,所以最好將上面的代碼重寫為以下形式:
int size = v . size();for(int I = 0;i++;我& lt尺寸)
雖然這是壹個簡單的改變,但它仍然贏得了性能。畢竟每壹個CPU周期都是寶貴的。
15.復制大量數據時,請使用System.arraycopy()命令。
16,代碼重構:增強代碼的可讀性。
例如:
公共類購物車{
私人列表購物車;
…
公共void添加(對象項目){
if(carts == null) {
carts = new ArrayList();
}
crts.add(項目);
}
公共void刪除(對象項目){
如果(推車。包含(項目)){
carts.remove(項目);
}
}
公共列表getCarts() {
//返回只讀列表
return collections . unmodifieblelist(購物車);
}
//不建議使用此方法。
//this.getCarts()。添加(項目);
}
17.創建壹個沒有new關鍵字的類的實例。
當使用new關鍵字創建類的實例時,將自動調用構造函數鏈中的所有構造函數。但是如果壹個對象實現了Cloneable接口,我們可以調用它的clone()方法。clone()方法不調用任何類構造函數。
當使用設計模式時,如果使用工廠模式來創建對象,那麽通過使用clone()方法來創建新的對象實例是非常簡單的。例如,以下是工廠模式的典型實現:
公共靜態信用getNewCredit() {
返回新信用();
}
改進後的代碼使用clone()方法,如下所示:
私有靜態信用基礎信用=新信用();
公共靜態信用getNewCredit() {
return(Credit)base Credit . clone();
}
上述思想對數組處理也是有用的。
18,乘除法
考慮以下代碼:
for(val = 0;val & lt100000;val +=5) {
alterX = val * 8;my result = val * 2;
}
用移位操作代替乘法操作可以大大提高性能。下面是修改後的代碼:
for(val = 0;val & lt100000;val += 5) {
alterX = val & lt& lt3;myResult = val & lt& lt1;
}
修改後的代碼不再做乘以8的運算,而是使用左移3位的等價運算,每左移1位就相當於乘以2。因此,將1位向右移位的操作相當於除以2。值得壹提的是,雖然shift操作速度很快,但可能會讓代碼更難理解,所以還是加點註釋比較好。
19.關閉JSP頁面中無用的會話。
壹個常見的誤解是會話是在有客戶端訪問時創建的,但事實是直到服務器端程序調用諸如httpservletrequest之類的語句時才創建會話。getsession (true)。請註意,如果JSP沒有顯示
session = http servlet request . getsession(true);這也是JSP中隱含的session對象的由來。因為session會消耗內存資源,所以如果不打算使用它,應該在所有JSP中關閉它。
對於那些不需要跟蹤會話狀態的頁面,關閉自動創建的會話可以節省壹些資源。使用以下頁面說明:
20.JDBC和輸入輸出
如果應用程序需要訪問大型數據集,它應該考慮使用塊提取。默認情況下,JDBC壹次獲取32行數據。例如,假設我們要遍歷壹個5000行的記錄集,JDBC必須調用數據庫157次來提取所有數據。如果塊大小更改為512,數據庫調用次數將減少到10。
[p][/p]21,Servlet和內存使用
許多開發人員隨意將大量信息保存到用戶會話中。有時,保存在會話中的對象沒有被垃圾收集機制及時回收。從性能的角度來看,典型的癥狀是用戶感覺系統周期性變慢,卻不能把原因歸結到任何具體的組件上。如果妳監控JVM的堆空間,它的表現就是內存占用異常上升下降。
解決這種內存問題主要有兩種方法。第壹種方法是在所有具有會話範圍的Bean中實現HttpSessionBindingListener接口。這樣,只要實現了valueUnbound()方法,就可以顯式釋放Bean使用的資源。另壹種方法是盡快取消對話。大多數應用服務器都有設置會話失效間隔的選項。此外,還可以通過編程方式調用會話的setMaxInactiveInterval()方法,該方法用於設置Servlet容器在會話失效之前允許的最大間隔時間(以秒為單位)。
22.使用緩沖標簽
壹些應用服務器增加了面向JSP的緩沖區標記功能。比如BEA的WebLogic Server從6.0版本開始就支持這個功能,Open。
Symphony Project也支持這個功能。JSP緩沖標簽可以緩沖頁面片段和整個頁面。當執行JSP頁面時,如果目標片段已經在緩沖區中,則不需要執行生成該片段的代碼。頁面級緩沖捕獲對指定URL的請求,並緩沖整個結果頁面。這個功能對於購物籃、目錄和門戶網站的主頁極其有用。對於這類應用,頁面級緩沖區可以保存頁面執行的結果,供後續請求使用。
23、選擇合適的參考機制。
在典型的JSP應用系統中,通常會提取頁眉和頁腳,然後根據需要引入頁眉和頁腳。目前將外部資源引入JSP頁面主要有兩種方法:包含指令和包含動作。
包括指令:例如
% & gt。這條指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁和指定的資源被合並到壹個文件中。引用的外部資源在編譯時確定,這比在運行時確定資源更有效。
包括動作:例如
/& gt;。此操作將引入執行指定頁面後生成的結果。因為是在運行時完成的,所以對輸出結果的控制更加靈活。但是,只有當引用的內容經常變化,或者在出現對主頁的請求之前無法確定引用的頁面時,使用include操作才是劃算的。
24.及時清除不再需要的會話。
為了清除不活動的會話,許多應用服務器的默認會話超時為30分鐘。當應用服務器需要保存更多的會話時,如果內存容量不足,操作系統會將壹些內存數據轉移到磁盤,應用服務器也可能會根據“最近使用”來使用
最近
Used)算法將壹些非活動會話轉儲到磁盤,甚至可能引發“內存不足”異常。在大規模系統中,序列化會話的成本非常昂貴。當不再需要會話時,應該調用httpsession。用於及時清除會話的()方法無效。httpsession.invalid()方法通常可以在應用程序的退出頁面上調用。
25.不要將數組聲明為:public static final。
26.HashMap遍歷效率的討論
有兩種方法可以遍歷HashMap中的鍵和值對:Map
................//第壹個周期
設置& lt字符串& gtappFieldDefIds = paramap . keyset();
for(String appFieldDefId:appFieldDefIds){
string[]values = paramap . get(appFieldDefId);
......
}
//第二個周期
for(Entry & lt;String,String[]& gt;entry : paraMap.entrySet()){
string appFieldDefId = entry . getkey();
string[]values = entry . getvalue();
.......
}
第壹種實現不如第二種高效。
分析如下:設置
代碼如下:
公共設置& ltK & gt密鑰集(){
設置& ltK & gtks =密鑰集;
返回(ks!= null?ks:(key set = new key set());
}
私有類鍵集擴展了AbstractSet & ltK & gt{
公共叠代器& ltK & gt叠代器(){
返回new key iterator();
}
public int size() {
返回大小;
}
公共布爾包含(對象o) {
return contains key(o);
}
public boolean remove(對象o) {
返回hashmap . this . removeentryforkey(o)!= null
}
公共void clear() {
hashmap . this . clear();
}
}
其實就是返回壹個私有類KeySet,繼承自AbstractSet,實現Set接口。
讓我們看看for/in循環的語法。
for(聲明:expression_r)
聲明
在實現階段,它被翻譯成以下幾類。
for(叠代器& ltE & gt#i = (expression_r)。叠代器();# I . hash next();){
declaration = # I . next();
聲明
}
因此,在for(string appfielddefid:appfielddefids)的第壹條語句中,HashMap.keySet()。調用iterator(),這個方法調用newKeyIterator()。
叠代器& ltK & gtnewKeyIterator() {
返回新的key iterator();
}
私有類KeyIterator擴展了HashIterator & ltK & gt{
public K next() {
返回nextEntry()。getKey();
}
}
所以它仍然被稱為。
在第二個循環中(條目
私有類EntryIterator擴展了HashIterator & lt地圖。Entry & ltk,V & gt& gt{
公開地圖。Entry & ltk,V & gt下壹個(){
返回next entry();
}
}
此時,第壹個循環獲得了鍵,第二個循環獲得了HashMap的條目。
效率是從循環中反映出來的第二個循環,可以直接取key和value值。
第壹個循環仍然需要使用HashMap的get(Object key)來獲取值。
現在看看HashMap的get(Object key)方法。
public V get(對象鍵){
object k = mask null(key);
int hash = hash(k);
int i = indexFor(hash,table . length);//Entry[]表
Entry & ltk,V & gte =表;
while (true) {
if (e == null)
返回null
if(e . hash = = hash & amp;& ampeq(k,e.key))
返回e.value
e = e . next;
}
}
其實就是再次使用哈希值取出對應的條目進行比較得到結果,所以使用第壹個中間循環相當於兩次輸入HashMap的條目。
第二個循環在得到Entry的值後直接取key和值,比第壹個循環效率高。其實按照Map的概念,用第二個循環應該更好,就是鍵和值的值對。這裏把key和value分開操作並不是壹個好的選擇。