java代碼優(yōu)化編程(2)
17、不用new關(guān)鍵詞創(chuàng)建類的實(shí)例
用new關(guān)鍵詞創(chuàng)建類的實(shí)例時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。但如果一個對象實(shí)現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會調(diào)用任何類構(gòu)造函數(shù)。
在使用設(shè)計(jì)模式(Design Pattern)的場合,如果用Factory模式創(chuàng)建對象,則改用clone()方法創(chuàng)建新的對象實(shí)例非常簡單。例如,下面是Factory模式的一個典型實(shí)現(xiàn):
public static Credit getNewCredit() {
return new Credit();
}
改進(jìn)后的代碼使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路對于數(shù)組處理同樣很有用。
18、乘法和除法
考慮下面的代碼:
for (val = 0; val < 100000; val +=5) {
alterX = val * 8; myResult = val * 2;
}
用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:
for (val = 0; val < 100000; val += 5) {
alterX = val << 3; myResult = val << 1;
}
修改后的代碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當(dāng)于乘以2。相應(yīng)地,右移1位操作相當(dāng)于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。
19、在JSP頁面中關(guān)閉無用的會話。
一個常見的誤解是以為session在有客戶端訪問時就被創(chuàng)建,然而事實(shí)是直到某server端程序調(diào)用 HttpServletRequest.getSession(true)這樣的語句時才被創(chuàng)建,注意如果JSP沒有顯示的使用 <%@page session="false"%> 關(guān)閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession session = HttpServletRequest.getSession(true);這也是JSP中隱含的 session對象的來歷。由于session會消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。
對于那些無需跟蹤會話狀態(tài)的頁面,關(guān)閉自動創(chuàng)建的會話可以節(jié)省一些資源。使用如下page指令:<%@ page session="false"%>
20、JDBC與I/O
如果應(yīng)用程序需要訪問一個規(guī)模很大的數(shù)據(jù)集,則應(yīng)當(dāng)考慮使用塊提取方式。默認(rèn)情況下,JDBC每次提取32行數(shù)據(jù)。舉例來說,假設(shè)我們要遍歷一個5000行的記錄集,JDBC必須調(diào)用數(shù)據(jù)庫157次才能提取到全部數(shù)據(jù)。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫的次數(shù)將減少到10次。
[p][/p]21、Servlet與內(nèi)存使用
許多開發(fā)者隨意地把大量信息保存到用戶會話之中。一些時候,保存在會話中的對象沒有及時地被垃圾回收機(jī)制回收。從性能上看,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個具體的組件。如果監(jiān)視JVM的堆空間,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。
解決這類內(nèi)存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會話的Bean中實(shí)現(xiàn)HttpSessionBindingListener接口。這樣,只要實(shí)現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源。 另外一種辦法就是盡快地把會話作廢。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會話作廢間隔時間的選項(xiàng)。另外,也可以用編程的方式調(diào)用會話的setMaxInactiveInterval()方法,該方法用來設(shè)定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計(jì)。
22、使用緩沖標(biāo)記
一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個功能,Open Symphony工程也同樣支持這個功能。JSP緩沖標(biāo)記既能夠緩沖頁面片斷,也能夠緩沖整個頁面。當(dāng)JSP頁面執(zhí)行時,如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行。頁面級緩沖捕獲對指定 URL的請求,并緩沖整個結(jié)果頁面。對于購物籃、目錄以及門戶網(wǎng)站的主頁來說,這個功能極其有用。對于這類應(yīng)用,頁面級緩沖能夠保存頁面執(zhí)行的結(jié)果,供后繼請求使用。
23、選擇合適的引用機(jī)制
在典型的JSP應(yīng)用系統(tǒng)中,頁頭、頁腳部分往往被抽取出來,然后根據(jù)需要引入頁頭、頁腳。當(dāng)前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。
include 指令:例如<%@ include file="copyright.html" %>。該指令在編譯時引入指定的資源。在編譯之前,帶有 include指令的頁面和指定的資源被合并成一個文件。被引用的外部資源在編譯時就確定,比運(yùn)行時才確定資源更高效。
include動作:例如<jsp:include page="copyright.jsp" />。該動作引入指定頁面執(zhí)行后生成的結(jié)果。由于它在運(yùn)行時完成,因此對輸出結(jié)果的控制更加靈活。但時,只有當(dāng)被引用的內(nèi)容頻繁地改變時,或者在對主頁面的請求沒有出現(xiàn)之前,被引用的頁面無法確定時,使用include 動作才合算。
24、及時清除不再需要的會話為了清除不再活動的會話,許多應(yīng)用服務(wù)器都有默認(rèn)的會話超時時間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會話時,如果內(nèi)存容量不足,操作系統(tǒng)會把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用 ”(Most Recently Used)算法把部分不活躍的會話轉(zhuǎn)儲到磁盤,甚至可能拋出“內(nèi)存不足”異常。在大規(guī)模系統(tǒng)中,串行化會話的代價是很昂貴的。當(dāng)會話不再需要時,應(yīng)當(dāng)及時調(diào)用HttpSession.invalidate()方法清除會話。 HttpSession.invalidate()方法通常可以在應(yīng)用的退出頁面調(diào)用。
25、不要將數(shù)組聲明為:public static final 。
26、HashMap的遍歷效率討論
經(jīng)常遇到對HashMap中的key和value值對的遍歷操作,有如下兩種方法:Map<String, String[]> paraMap = new HashMap<String, String[]>();
................//第一個循環(huán)
Set<String> appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
......
}
//第二個循環(huán)
for(Entry<String, String[]> entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
第一種實(shí)現(xiàn)明顯的效率不如第二種實(shí)現(xiàn)。
分析如下 Set<String> appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet
代碼如下:
public Set<K> keySet() {
Set<K> ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
其實(shí)就是返回一個私有類KeySet, 它是從AbstractSet繼承而來,實(shí)現(xiàn)了Set接口。
再來看看for/in循環(huán)的語法
for(declaration : expression)
statement
在執(zhí)行階段被翻譯成如下各式
for(Iterator<E> #i = (expression).iterator(); #i.hashNext();){
declaration = #i.next();
statement
}
因此在第一個for語句for (String appFieldDefId : appFieldDefIds) 中調(diào)用了HashMap.keySet().iterator() 而這個方法調(diào)用了newKeyIterator()Iterator<K> newKeyIterator() {
return new KeyIterator();
}
private class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}
所以在for中還是調(diào)用了
在第二個循環(huán)for(Entry<String, String[]> entry : paraMap.entrySet())中使用的Iterator是如下的一個內(nèi)部類
private class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
此時第一個循環(huán)得到key,第二個循環(huán)得到HashMap的Entry
效率就是從循環(huán)里面體現(xiàn)出來的第二個循環(huán)此致可以直接取key和value值
而第一個循環(huán)還是得再利用HashMap的get(Object key)來取value值
現(xiàn)在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
Entry<K,V> e = table;
while (true) {
if (e == null)
return null;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
其實(shí)就是再次利用Hash值取出相應(yīng)的Entry做比較得到結(jié)果,所以使用第一中循環(huán)相當(dāng)于兩次進(jìn)入HashMap的Entry中
而第二個循環(huán)取得Entry的值之后直接取key和value,效率比第一個循環(huán)高。其實(shí)按照Map的概念來看也應(yīng)該是用第二個循環(huán)好一點(diǎn),它本來就是key和value的值對,將key和value分開操作在這里不是個好選擇。
27、array(數(shù)組) 和 ArryList的使用
array([]):最高效;但是其容量固定且無法動態(tài)改變;
ArrayList:容量可動態(tài)增長;但犧牲效率;
基于效率和類型檢驗(yàn),應(yīng)盡可能使用array,無法確定數(shù)組大小時才使用ArrayList!
ArrayList是Array的復(fù)雜版本
ArrayList內(nèi)部封裝了一個Object類型的數(shù)組,從一般的意義來說,它和數(shù)組沒有本質(zhì)的差別,甚至于ArrayList的許多方法,如Index、IndexOf、Contains、Sort等都是在內(nèi)部數(shù)組的基礎(chǔ)上直接調(diào)用Array的對應(yīng)方法。
ArrayList存入對象時,拋棄類型信息,所有對象屏蔽為Object,編譯時不檢查類型,但是運(yùn)行時會報錯。
注:jdk5中加入了對泛型的支持,已經(jīng)可以在使用ArrayList時進(jìn)行類型檢查。
從這一點(diǎn)上看來,ArrayList與數(shù)組的區(qū)別主要就是由于動態(tài)增容的效率問題了
28、盡量使用HashMap 和ArrayList ,除非必要,否則不推薦使用HashTable和Vector ,后者由于使用同步機(jī)制,而導(dǎo)致了性能的開銷。
29、StringBuffer 和StringBuilder的區(qū)別:
java.lang.StringBuffer 線程安全的可變字符序列。一個類似于 String 的字符串緩沖區(qū),但不能修改。StringBuilder。與該類相比,通常應(yīng)該優(yōu)先使用 java.lang.StringBuilder類,因?yàn)樗С炙邢嗤牟僮鳎捎谒粓?zhí)行同步,所以速度更快。為了獲得更好的性能,在構(gòu)造 StirngBuffer 或 StirngBuilder 時應(yīng)盡可能指定它的容量。當(dāng)然,如果你操作的字符串長度不超過 16 個字符就不用了。 相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%-15% 左右的性能提升,但卻要冒多線程不安全的風(fēng)險。而在現(xiàn)實(shí)的模塊化編程中,負(fù)責(zé)某一模塊的程序員不一定能清晰地判斷該模塊是否會放入多線程的環(huán)境中運(yùn)行,因此:除非你能確定你的系統(tǒng)的瓶頸是在 StringBuffer 上,并且確定你的模塊不會運(yùn)行在多線程模式下,否則還是用 StringBuffer 吧。