一、通用篇
1.1 不用new關(guān)鍵詞創(chuàng)建類的實(shí)例
1.2 使用非阻塞I/O
1.3 慎用異常
1.4 不要重復(fù)初始化變量
1.5 盡量指定類的final修飾符
1.6 盡量使用局部變量
1.7 乘法和除法
二齐鲤、J2EE篇
2.1 使用緩沖標(biāo)記
2.2 始終通過會(huì)話Bean訪問實(shí)體Bean
2.3 選擇合適的引用機(jī)制
2.4 在部署描述器中設(shè)置只讀屬性
2.5 緩沖對(duì)EJB Home的訪問
2.6 為EJB實(shí)現(xiàn)本地接口
2.7 生成主鍵
2.8 及時(shí)清除不再需要的會(huì)話
2.9 在JSP頁面中關(guān)閉無用的會(huì)話
2.10 Servlet與內(nèi)存使用
2.11 HTTP Keep-Alive
2.12 JDBC與Unicode
2.13 JDBC與I/O
1.14 內(nèi)存數(shù)據(jù)庫(kù)
三黄绩、GUI篇
3.1 用JAR壓縮類文件
3.2 提示Applet裝入進(jìn)程
3.3 在畫出圖形之前預(yù)先裝入它
3.4 覆蓋update方法
3.5 延遲重畫操作
3.6 使用雙緩沖區(qū)
3.7 使用BufferedImage
3.8 使用VolatileImage
3.9 使用Window Blitting
四乘陪、補(bǔ)充資料
===================================
正文:
===================================
一壳繁、通用篇
“通用篇”討論的問題適合于大多數(shù)Java應(yīng)用勒奇。
1.1 不用new關(guān)鍵詞創(chuàng)建類的實(shí)例
用new關(guān)鍵詞創(chuàng)建類的實(shí)例時(shí)甩十,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會(huì)被自動(dòng)調(diào)用。但如果一個(gè)對(duì)象實(shí)現(xiàn)了Cloneable接口荧缘,我們可以調(diào)用它的clone()方法皆警。clone()方法不會(huì)調(diào)用任何類構(gòu)造函數(shù)。
在使用設(shè)計(jì)模式(Design Pattern)的場(chǎng)合截粗,如果用Factory模式創(chuàng)建對(duì)象信姓,則改用clone()方法創(chuàng)建新的對(duì)象實(shí)例非常簡(jiǎn)單。例如绸罗,下面是Factory模式的一個(gè)典型實(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();
}
上面的思路對(duì)于數(shù)組處理同樣很有用。
1.2 使用非阻塞I/O
版本較低的JDK不支持非阻塞I/O API珊蟀。為避免I/O阻塞菊值,一些應(yīng)用采用了創(chuàng)建大量線程的辦法(在較好的情況下外驱,會(huì)使用一個(gè)緩沖池)。這種技術(shù)可以在許多必須支持并發(fā)I/O流的應(yīng)用中見到腻窒,如Web服務(wù)器昵宇、報(bào)價(jià)和拍賣應(yīng)用等。然而儿子,創(chuàng)建Java線程需要相當(dāng)可觀的開銷瓦哎。
JDK 1.4引入了非阻塞的I/O庫(kù)(java.nio)。如果應(yīng)用要求使用版本較早的JDK柔逼,在這里有一個(gè)支持非阻塞I/O的軟件包蒋譬。
請(qǐng)參見Sun中國(guó)網(wǎng)站的《調(diào)整Java的I/O性能》。
1.3 慎用異常
異常對(duì)性能不利愉适。拋出異常首先要?jiǎng)?chuàng)建一個(gè)新的對(duì)象犯助。Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧维咸,收集調(diào)用跟蹤信息剂买。只要有異常被拋出,VM就必須調(diào)整調(diào)用堆棧腰湾,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對(duì)象雷恃。
異常只能用于錯(cuò)誤處理,不應(yīng)該用來控制程序流程费坊。
1.4 不要重復(fù)初始化變量
默認(rèn)情況下倒槐,調(diào)用類的構(gòu)造函數(shù)時(shí), Java會(huì)把變量初始化成確定的值:所有的對(duì)象被設(shè)置成null附井,整數(shù)變量(byte讨越、short、int永毅、long)設(shè)置成0把跨,float和 double變可柚貿(mào)?.0,邏輯值設(shè)置成false沼死。當(dāng)一個(gè)類從另一個(gè)類派生時(shí)着逐,這一點(diǎn)尤其應(yīng)該注意,因?yàn)橛胣ew關(guān)鍵詞創(chuàng)建一個(gè)對(duì)象時(shí)意蛀,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會(huì)被自動(dòng)調(diào)用耸别。
1.5 盡量指定類的final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中县钥,有許多應(yīng)用final的例子秀姐,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法若贮。
另外省有,如果指定一個(gè)類為final痒留,則該類所有的方法都是final。Java編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實(shí)現(xiàn)有關(guān))蠢沿。此舉能夠使性能平均提高50%伸头。
1.6 盡量使用局部變量
調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧(Stack)中,速度較快舷蟀。其他變量熊锭,如靜態(tài)變量、實(shí)例變量等雪侥,都在堆(Heap)中創(chuàng)建,速度較慢精绎。另外速缨,依賴于具體的編譯器/JVM,局部變量還可能得到進(jìn)一步優(yōu)化代乃。請(qǐng)參見《盡可能使用堆棧變量》旬牲。
1.7 乘法和除法
考慮下面的代碼:
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的操作搁吓,而是改用等價(jià)的左移3位操作原茅,每左移1位相當(dāng)于乘以2。相應(yīng)地堕仔,右移1位操作相當(dāng)于除以2擂橘。值得一提的是,雖然移位操作速度快摩骨,但可能使代碼比較難于理解通贞,所以最好加上一些注釋。
二恼五、J2EE篇
前面介紹的改善性能技巧適合于大多數(shù)Java應(yīng)用昌罩,接下來要討論的問題適合于使用JSP、EJB或JDBC的應(yīng)用灾馒。
2.1 使用緩沖標(biāo)記
一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能茎用。例如,BEA的WebLogic Server從6.0版本開始支持這個(gè)功能睬罗,Open Symphony工程也同樣支持這個(gè)功能轨功。JSP緩沖標(biāo)記既能夠緩沖頁面片斷,也能夠緩沖整個(gè)頁面傅物。當(dāng)JSP頁面執(zhí)行時(shí)夯辖,如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行董饰。頁面級(jí)緩沖捕獲對(duì)指定URL的請(qǐng)求蒿褂,并緩沖整個(gè)結(jié)果頁面圆米。對(duì)于購(gòu)物籃、目錄以及門戶網(wǎng)站的主頁來說啄栓,這個(gè)功能極其有用娄帖。對(duì)于這類應(yīng)用,頁面級(jí)緩沖能夠保存頁面執(zhí)行的結(jié)果昙楚,供后繼請(qǐng)求使用近速。
對(duì)于代碼邏輯復(fù)雜的頁面,利用緩沖標(biāo)記提高性能的效果比較明顯堪旧;反之削葱,效果可能略遜一籌。
請(qǐng)參見《用緩沖技術(shù)提高JSP應(yīng)用的性能和穩(wěn)定性》淳梦。
2.2 始終通過會(huì)話Bean訪問實(shí)體Bean
直接訪問實(shí)體Bean不利于性能析砸。當(dāng)客戶程序遠(yuǎn)程訪問實(shí)體Bean時(shí),每一個(gè)get方法都是一個(gè)遠(yuǎn)程調(diào)用爆袍。訪問實(shí)體Bean的會(huì)話Bean是本地的首繁,能夠把所有數(shù)據(jù)組織成一個(gè)結(jié)構(gòu),然后返回它的值陨囊。
用會(huì)話Bean封裝對(duì)實(shí)體Bean的訪問能夠改進(jìn)事務(wù)管理弦疮,因?yàn)闀?huì)話Bean只有在到達(dá)事務(wù)邊界時(shí)才會(huì)提交。每一個(gè)對(duì)get方法的直接調(diào)用產(chǎn)生一個(gè)事務(wù)蜘醋,容器將在每一個(gè)實(shí)體Bean的事務(wù)之后執(zhí)行一個(gè)“裝入-讀取”操作胁塞。
一些時(shí)候,使用實(shí)體Bean會(huì)導(dǎo)致程序性能不佳堂湖。如果實(shí)體Bean的唯一用途就是提取和更新數(shù)據(jù)闲先,改成在會(huì)話Bean之內(nèi)利用JDBC訪問數(shù)據(jù)庫(kù)可以得到更好的性能。
2.3 選擇合適的引用機(jī)制
在典型的JSP應(yīng)用系統(tǒng)中无蜂,頁頭伺糠、頁腳部分往往被抽取出來,然后根據(jù)需要引入頁頭斥季、頁腳训桶。當(dāng)前,在JSP頁面中引入外部資源的方法主要有兩種:include指令酣倾,以及include動(dòng)作舵揭。
include指令:例如<%@ include file="copyright.html" %>。該指令在編譯時(shí)引入指定的資源躁锡。在編譯之前午绳,帶有include指令的頁面和指定的資源被合并成一個(gè)文件。被引用的外部資源在編譯時(shí)就確定映之,比運(yùn)行時(shí)才確定資源更高效拦焚。
include動(dòng)作:例如<jsp:include page="copyright.jsp" />蜡坊。該動(dòng)作引入指定頁面執(zhí)行后生成的結(jié)果。由于它在運(yùn)行時(shí)完成赎败,因此對(duì)輸出結(jié)果的控制更加靈活秕衙。但時(shí),只有當(dāng)被引用的內(nèi)容頻繁地改變時(shí)僵刮,或者在對(duì)主頁面的請(qǐng)求沒有出現(xiàn)之前据忘,被引用的頁面無法確定時(shí),使用include動(dòng)作才合算搞糕。
2.4 在部署描述器中設(shè)置只讀屬性
實(shí)體Bean的部署描述器允許把所有g(shù)et方法設(shè)置成“只讀”勇吊。當(dāng)某個(gè)事務(wù)單元的工作只包含執(zhí)行讀取操作的方法時(shí),設(shè)置只讀屬性有利于提高性能窍仰,因?yàn)槿萜鞑槐卦賵?zhí)行存儲(chǔ)操作萧福。
2.5 緩沖對(duì)EJB Home的訪問
EJB Home接口通過JNDI名稱查找獲得。這個(gè)操作需要相當(dāng)可觀的開銷辈赋。JNDI查找最好放入Servlet的init()方法里面。如果應(yīng)用中多處頻繁地出現(xiàn)EJB訪問膏燕,最好創(chuàng)建一個(gè)EJBHomeCache類钥屈。EJBHomeCache類一般應(yīng)該作為singleton實(shí)現(xiàn)。
2.6 為EJB實(shí)現(xiàn)本地接口
本地接口是EJB 2.0規(guī)范新增的內(nèi)容坝辫,它使得Bean能夠避免遠(yuǎn)程調(diào)用的開銷篷就。請(qǐng)考慮下面的代碼。
PayBeanHome home = (PayBeanHome)
javax.rmi.PortableRemoteObject.narrow
(ctx.lookup ("PayBeanHome"), PayBeanHome.class);
PayBean bean = (PayBean)
javax.rmi.PortableRemoteObject.narrow
(home.create(), PayBean.class);
第一個(gè)語句表示我們要尋找Bean的Home接口近忙。這個(gè)查找通過JNDI進(jìn)行竭业,它是一個(gè)RMI調(diào)用。然后及舍,我們定位遠(yuǎn)程對(duì)象未辆,返回代理引用,這也是一個(gè) RMI調(diào)用锯玛。第二個(gè)語句示范了如何創(chuàng)建一個(gè)實(shí)例咐柜,涉及了創(chuàng)建IIOP請(qǐng)求并在網(wǎng)絡(luò)上傳輸請(qǐng)求的stub程序,它也是一個(gè)RMI調(diào)用攘残。
要實(shí)現(xiàn)本地接口拙友,我們必須作如下修改:
方法不能再拋出java.rmi.RemoteException異常,包括從RemoteException派生的異常歼郭,比如 TransactionRequiredException遗契、TransactionRolledBackException和 NoSuchObjectException。EJB提供了等價(jià)的本地異常病曾,如TransactionRequiredLocalException牍蜂、 TransactionRolledBackLocalException和NoSuchObjectLocalException漾根。
所有數(shù)據(jù)和返回值都通過引用的方式傳遞,而不是傳遞值捷兰。
本地接口必須在EJB部署的機(jī)器上使用立叛。簡(jiǎn)而言之,客戶程序和提供服務(wù)的組件必須在同一個(gè)JVM上運(yùn)行贡茅。
如果Bean實(shí)現(xiàn)了本地接口秘蛇,則其引用不可串行化。
請(qǐng)參見《用本地引用提高EJB訪問效率》顶考。
2.7 生成主鍵
在EJB之內(nèi)生成主鍵有許多途徑赁还,下面分析了幾種常見的辦法以及它們的特點(diǎn)。
利用數(shù)據(jù)庫(kù)內(nèi)建的標(biāo)識(shí)機(jī)制(SQL Server的IDENTITY或Oracle的SEQUENCE)驹沿。這種方法的缺點(diǎn)是EJB可移植性差艘策。
由實(shí)體Bean自己計(jì)算主鍵值(比如做增量操作)。它的缺點(diǎn)是要求事務(wù)可串行化渊季,而且速度也較慢朋蔫。
利用NTP之類的時(shí)鐘服務(wù)。這要求有面向特定平臺(tái)的本地代碼却汉,從而把Bean固定到了特定的OS之上驯妄。另外,它還導(dǎo)致了這樣一種可能合砂,即在多CPU的服務(wù)器上青扔,同一個(gè)毫秒之內(nèi)生成了兩個(gè)主鍵。
借鑒Microsoft的思路翩伪,在Bean中創(chuàng)建一個(gè)GUID微猖。然而,如果不求助于JNI缘屹,Java不能確定網(wǎng)卡的MAC地址凛剥;如果使用JNI,則程序就要依賴于特定的OS轻姿。
還有其他幾種辦法当悔,但這些辦法同樣都有各自的局限。似乎只有一個(gè)答案比較理想:結(jié)合運(yùn)用RMI和JNDI踢代。先通過RMI注冊(cè)把RMI遠(yuǎn)程對(duì)象綁定到JNDI樹盲憎。客戶程序通過JNDI進(jìn)行查找胳挎。下面是一個(gè)例子:
public class keyGenerator extends UnicastRemoteObject implements Remote {
private static long KeyValue = System.currentTimeMillis();
public static synchronized long getKey() throws RemoteException { return KeyValue++; }
2.8 及時(shí)清除不再需要的會(huì)話
為了清除不再活動(dòng)的會(huì)話饼疙,許多應(yīng)用服務(wù)器都有默認(rèn)的會(huì)話超時(shí)時(shí)間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會(huì)話時(shí)窑眯,如果內(nèi)存容量不足屏积,操作系統(tǒng)會(huì)把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用”(Most Recently Used)算法把部分不活躍的會(huì)話轉(zhuǎn)儲(chǔ)到磁盤磅甩,甚至可能拋出“內(nèi)存不足”異常炊林。在大規(guī)模系統(tǒng)中,串行化會(huì)話的代價(jià)是很昂貴的卷要。當(dāng)會(huì)話不再需要時(shí)渣聚,應(yīng)當(dāng)及時(shí)調(diào)用HttpSession.invalidate()方法清除會(huì)話。HttpSession.invalidate()方法通成妫可以在應(yīng)用的退出頁面調(diào)用奕枝。
2.9 在JSP頁面中關(guān)閉無用的會(huì)話
對(duì)于那些無需跟蹤會(huì)話狀態(tài)的頁面,關(guān)閉自動(dòng)創(chuàng)建的會(huì)話可以節(jié)省一些資源瓶堕。使用如下page指令:
<%@ page session="false"%>
2.10 Servlet與內(nèi)存使用
許多開發(fā)者隨意地把大量信息保存到用戶會(huì)話之中隘道。一些時(shí)候,保存在會(huì)話中的對(duì)象沒有及時(shí)地被垃圾回收機(jī)制回收郎笆。從性能上看谭梗,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個(gè)具體的組件宛蚓。如果監(jiān)視JVM的堆空間默辨,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。
解決這類內(nèi)存問題主要有二種辦法苍息。第一種辦法是,在所有作用范圍為會(huì)話的Bean中實(shí)現(xiàn)HttpSessionBindingListener接口壹置。這樣竞思,只要實(shí)現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源钞护。
另外一種辦法就是盡快地把會(huì)話作廢盖喷。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會(huì)話作廢間隔時(shí)間的選項(xiàng)。另外难咕,也可以用編程的方式調(diào)用會(huì)話的 setMaxInactiveInterval()方法课梳,該方法用來設(shè)定在作廢會(huì)話之前,Servlet容器允許的客戶請(qǐng)求的最大間隔時(shí)間余佃,以秒計(jì)暮刃。
2.11 HTTP Keep-Alive
Keep-Alive功能使客戶端到服務(wù)器端的連接持續(xù)有效,當(dāng)出現(xiàn)對(duì)服務(wù)器的后繼請(qǐng)求時(shí)爆土,Keep-Alive功能避免了建立或者重新建立連接椭懊。市場(chǎng)上的大部分Web服務(wù)器,包括iPlanet步势、IIS和Apache氧猬,都支持HTTP Keep-Alive背犯。對(duì)于提供靜態(tài)內(nèi)容的網(wǎng)站來說,這個(gè)功能通常很有用盅抚。但是漠魏,對(duì)于負(fù)擔(dān)較重的網(wǎng)站來說,這里存在另外一個(gè)問題:雖然為客戶保留打開的連接有一定的好處妄均,但它同樣影響了性能柱锹,因?yàn)樵谔幚頃和F陂g,本來可以釋放的資源仍舊被占用丛晦。當(dāng)Web服務(wù)器和應(yīng)用服務(wù)器在同一臺(tái)機(jī)器上運(yùn)行時(shí)奕纫,Keep- Alive功能對(duì)資源利用的影響尤其突出。
2.12 JDBC與Unicode
想必你已經(jīng)了解一些使用JDBC時(shí)提高性能的措施烫沙,比如利用連接池匹层、正確地選擇存儲(chǔ)過程和直接執(zhí)行的SQL、從結(jié)果集刪除多余的列锌蓄、預(yù)先編譯SQL語句升筏,等等。
除了這些顯而易見的選擇之外瘸爽,另一個(gè)提高性能的好選擇可能就是把所有的字符數(shù)據(jù)都保存為Unicode(代碼頁13488)您访。Java以Unicode形式處理所有數(shù)據(jù),因此剪决,數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序不必再執(zhí)行轉(zhuǎn)換過程灵汪。但應(yīng)該記住:如果采用這種方式柑潦,數(shù)據(jù)庫(kù)會(huì)變得更大享言,因?yàn)槊總€(gè)Unicode字符需要2個(gè)字節(jié)存儲(chǔ)空間。另外渗鬼,如果有其他非Unicode的程序訪問數(shù)據(jù)庫(kù)览露,性能問題仍舊會(huì)出現(xiàn),因?yàn)檫@時(shí)數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序仍舊必須執(zhí)行轉(zhuǎn)換過程譬胎。
2.13 JDBC與I/O
如果應(yīng)用程序需要訪問一個(gè)規(guī)模很大的數(shù)據(jù)集差牛,則應(yīng)當(dāng)考慮使用塊提取方式。默認(rèn)情況下堰乔,JDBC每次提取32行數(shù)據(jù)偏化。舉例來說,假設(shè)我們要遍歷一個(gè)5000 行的記錄集镐侯,JDBC必須調(diào)用數(shù)據(jù)庫(kù)157次才能提取到全部數(shù)據(jù)夹孔。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫(kù)的次數(shù)將減少到10次。
在一些情形下這種技術(shù)無效搭伤。例如只怎,如果使用可滾動(dòng)的記錄集,或者在查詢中指定了FOR UPDATE怜俐,則塊操作方式不再有效身堡。
1.14 內(nèi)存數(shù)據(jù)庫(kù)
許多應(yīng)用需要以用戶為單位在會(huì)話對(duì)象中保存相當(dāng)數(shù)量的數(shù)據(jù),典型的應(yīng)用如購(gòu)物籃和目錄等拍鲤。由于這類數(shù)據(jù)可以按照行/列的形式組織贴谎,因此,許多應(yīng)用創(chuàng)建了龐大的Vector或HashMap季稳。在會(huì)話中保存這類數(shù)據(jù)極大地限制了應(yīng)用的可伸縮性擅这,因?yàn)榉?wù)器擁有的內(nèi)存至少必須達(dá)到每個(gè)會(huì)話占用的內(nèi)存數(shù)量乘以并發(fā)用戶最大數(shù)量,它不僅使服務(wù)器價(jià)格昂貴景鼠,而且垃圾收集的時(shí)間間隔也可能延長(zhǎng)到難以忍受的程度仲翎。
一些人把購(gòu)物籃/目錄功能轉(zhuǎn)移到數(shù)據(jù)庫(kù)層,在一定程度上提高了可伸縮性铛漓。然而溯香,把這部分功能放到數(shù)據(jù)庫(kù)層也存在問題,且問題的根源與大多數(shù)關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)的體系結(jié)構(gòu)有關(guān)浓恶。對(duì)于關(guān)系數(shù)據(jù)庫(kù)來說玫坛,運(yùn)行時(shí)的重要原則之一是確保所有的寫入操作穩(wěn)定、可靠包晰,因而湿镀,所有的性能問題都與物理上把數(shù)據(jù)寫入磁盤的能力有關(guān)。關(guān)系數(shù)據(jù)庫(kù)力圖減少I/O操作伐憾,特別是對(duì)于讀操作勉痴,但實(shí)現(xiàn)該目標(biāo)的主要途徑只是執(zhí)行一套實(shí)現(xiàn)緩沖機(jī)制的復(fù)雜算法,而這正是數(shù)據(jù)庫(kù)層第一號(hào)性能瓶頸通橙總是 CPU的主要原因。
一種替代傳統(tǒng)關(guān)系數(shù)據(jù)庫(kù)的方案是嘴瓤,使用在內(nèi)存中運(yùn)行的數(shù)據(jù)庫(kù)(In-memory Database)扫外,例如TimesTen。內(nèi)存數(shù)據(jù)庫(kù)的出發(fā)點(diǎn)是允許數(shù)據(jù)臨時(shí)地寫入廓脆,但這些數(shù)據(jù)不必永久地保存到磁盤上筛谚,所有的操作都在內(nèi)存中進(jìn)行。這樣停忿,內(nèi)存數(shù)據(jù)庫(kù)不需要復(fù)雜的算法來減少I/O操作驾讲,而且可以采用比較簡(jiǎn)單的加鎖機(jī)制,因而速度很快。
三吮铭、GUI篇
這一部分介紹的內(nèi)容適合于圖形用戶界面的應(yīng)用(Applet和普通應(yīng)用)时迫,要用到AWT或Swing。
3.1 用JAR壓縮類文件
Java檔案文件(JAR文件)是根據(jù)JavaBean標(biāo)準(zhǔn)壓縮的文件谓晌,是發(fā)布JavaBean組件的主要方式和推薦方式掠拳。JAR檔案有助于減少文件體積,縮短下載時(shí)間纸肉。例如溺欧,它有助于Applet提高啟動(dòng)速度。一個(gè)JAR文件可以包含一個(gè)或者多個(gè)相關(guān)的Bean以及支持文件柏肪,比如圖形姐刁、聲音、HTML 和其他資源烦味。
要在HTML/JSP文件中指定JAR文件聂使,只需在Applet標(biāo)記中加入ARCHIVE = "name.jar"聲明。
請(qǐng)參見《使用檔案文件提高 applet 的加載速度》拐叉。
3.2 提示Applet裝入進(jìn)程
你是否看到過使用Applet的網(wǎng)站岩遗,注意到在應(yīng)該運(yùn)行Applet的地方出現(xiàn)了一個(gè)占位符?當(dāng)Applet的下載時(shí)間較長(zhǎng)時(shí)凤瘦,會(huì)發(fā)生什么事情宿礁?最大的可能就是用戶掉頭離去。在這種情況下蔬芥,顯示一個(gè)Applet正在下載的信息無疑有助于鼓勵(lì)用戶繼續(xù)等待梆靖。
下面我們來看看一種具體的實(shí)現(xiàn)方法。首先創(chuàng)建一個(gè)很小的Applet笔诵,該Applet負(fù)責(zé)在后臺(tái)下載正式的Applet:
import java.applet.Applet;
import java.applet.AppletStub;
import java.awt.Label;
import java.awt.Graphics;
import java.awt.GridLayout;
public class PreLoader extends Applet implements Runnable, AppletStub {
String largeAppletName;
Label label;
public void init() {
// 要求裝載的正式Applet
largeAppletName = getParameter("applet");
// “請(qǐng)稍等”提示信息
label = new Label("請(qǐng)稍等..." + largeAppletName);
add(label);
}
public void run(){
try {
// 獲得待裝載Applet的類
Class largeAppletClass = Class.forName(largeAppletName);
// 創(chuàng)建待裝載Applet的實(shí)例
Applet largeApplet = (Applet)largeAppletClass.newInstance();
// 設(shè)置該Applet的Stub程序
largeApplet.setStub(this);
// 取消“請(qǐng)稍等”信息
remove(label);
// 設(shè)置布局
setLayout(new GridLayout(1, 0));
add(largeApplet);
// 顯示正式的Applet
largeApplet.init();
largeApplet.start();
}
catch (Exception ex) {
// 顯示錯(cuò)誤信息
label.setText("不能裝入指定的Applet");
}
// 刷新屏幕
validate();
}
public void appletResize(int width, int height) {
// 把a(bǔ)ppletResize調(diào)用從stub程序傳遞到Applet
resize(width, height);
}
}
編譯后的代碼小于2K返吻,下載速度很快。代碼中有幾個(gè)地方值得注意乎婿。首先测僵,PreLoader實(shí)現(xiàn)了AppletStub接口。一般地谢翎,Applet從調(diào)用者判斷自己的codebase捍靠。在本例中,我們必須調(diào)用setStub()告訴Applet到哪里提取這個(gè)信息森逮。另一個(gè)值得注意的地方是榨婆, AppletStub接口包含許多和Applet類一樣的方法,但appletResize()方法除外褒侧。這里我們把對(duì)appletResize()方法的調(diào)用傳遞給了resize()方法良风。
3.3 在畫出圖形之前預(yù)先裝入它
ImageObserver接口可用來接收?qǐng)D形裝入的提示信息谊迄。ImageObserver接口只有一個(gè)方法imageUpdate(),能夠用一次repaint()操作在屏幕上畫出圖形烟央。下面提供了一個(gè)例子统诺。
public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
if ((flags & ALLBITS) !=0 {
repaint();
}
else if (flags & (ERROR |ABORT )) != 0) {
error = true;
// 文件沒有找到,考慮顯示一個(gè)占位符
repaint();
}
return (flags & (ALLBITS | ERROR| ABORT)) == 0;
}
當(dāng)圖形信息可用時(shí)吊档,imageUpdate()方法被調(diào)用篙议。如果需要進(jìn)一步更新,該方法返回true怠硼;如果所需信息已經(jīng)得到鬼贱,該方法返回false。
3.4 覆蓋update方法
update()方法的默認(rèn)動(dòng)作是清除屏幕香璃,然后調(diào)用paint()方法这难。如果使用默認(rèn)的update()方法,頻繁使用圖形的應(yīng)用可能出現(xiàn)顯示閃爍現(xiàn)象葡秒。要避免在paint()調(diào)用之前的屏幕清除操作姻乓,只需按照如下方式覆蓋update()方法:
public void update(Graphics g) {
paint(g);
}
更理想的方案是:覆蓋update(),只重畫屏幕上發(fā)生變化的區(qū)域眯牧,如下所示:
public void update(Graphics g) {
g.clipRect(x, y, w, h);
paint(g);
}
3.5 延遲重畫操作
對(duì)于圖形用戶界面的應(yīng)用來說蹋岩,性能低下的主要原因往往可以歸結(jié)為重畫屏幕的效率低下。當(dāng)用戶改變窗口大小或者滾動(dòng)一個(gè)窗口時(shí)学少,這一點(diǎn)通臣舾觯可以很明顯地觀察到。改變窗口大小或者滾動(dòng)屏幕之類的操作導(dǎo)致重畫屏幕事件大量地版确、快速地生成扣囊,甚至超過了相關(guān)代碼的執(zhí)行速度。對(duì)付這個(gè)問題最好的辦法是忽略所有“遲到” 的事件绒疗。
建議在這里引入一個(gè)數(shù)毫秒的時(shí)差侵歇,即如果我們立即接收到了另一個(gè)重畫事件,可以停止處理當(dāng)前事件轉(zhuǎn)而處理最后一個(gè)收到的重畫事件吓蘑;否則惕虑,我們繼續(xù)進(jìn)行當(dāng)前的重畫過程。
如果事件要啟動(dòng)一項(xiàng)耗時(shí)的工作磨镶,分離出一個(gè)工作線程是一種較好的處理方式溃蔫;否則,一些部件可能被“凍結(jié)”棋嘲,因?yàn)槊看沃荒芴幚硪粋€(gè)事件酒唉。下面提供了一個(gè)事件處理的簡(jiǎn)單例子矩桂,但經(jīng)過擴(kuò)展后它可以用來控制工作線程沸移。
public static void runOnce(String id, final long milliseconds) {
synchronized(e_queue) { // e_queue: 所有事件的集合
if (!e_queue.containsKey(id)) {
e_queue.put(token, new LastOne());
}
}
final LastOne lastOne = (LastOne) e_queue.get(token);
final long time = System.currentTimeMillis(); // 獲得當(dāng)前時(shí)間
lastOne.time = time;
(new Thread() {public void run() {
if (milliseconds > 0) {
try {Thread.sleep(milliseconds);} // 暫停線程
catch (Exception ex) {}
}
synchronized(lastOne.running) { // 等待上一事件結(jié)束
if (lastOne.time != time) // 只處理最后一個(gè)事件
return;
}
}}).start();
}
private static Hashtable e_queue = new Hashtable();
private static class LastOne {
public long time=0;
public Object running = new Object();
}
3.6 使用雙緩沖區(qū)
在屏幕之外的緩沖區(qū)繪圖痪伦,完成后立即把整個(gè)圖形顯示出來。由于有兩個(gè)緩沖區(qū)雹锣,所以程序可以來回切換网沾。這樣,我們可以用一個(gè)低優(yōu)先級(jí)的線程負(fù)責(zé)畫圖蕊爵,使得程序能夠利用空閑的CPU時(shí)間執(zhí)行其他任務(wù)辉哥。下面的偽代碼片斷示范了這種技術(shù)。
Graphics myGraphics;
Image myOffscreenImage = createImage(size().width, size().height);
Graphics offscreenGraphics = myOffscreenImage.getGraphics();
offscreenGraphics.drawImage(img, 50, 50, this);
myGraphics.drawImage(myOffscreenImage, 0, 0, this);
3.7 使用BufferedImage
Java JDK 1.2使用了一個(gè)軟顯示設(shè)備攒射,使得文本在不同的平臺(tái)上看起來相似醋旦。為實(shí)現(xiàn)這個(gè)功能,Java必須直接處理構(gòu)成文字的像素会放。由于這種技術(shù)要在內(nèi)存中大量地進(jìn)行位復(fù)制操作饲齐,早期的JDK在使用這種技術(shù)時(shí)性能不佳。為解決這個(gè)問題而提出的Java標(biāo)準(zhǔn)實(shí)現(xiàn)了一種新的圖形類型咧最,即BufferedImage捂人。
BufferedImage子類描述的圖形帶有一個(gè)可訪問的圖形數(shù)據(jù)緩沖區(qū)。一個(gè)BufferedImage包含一個(gè)ColorModel和一組光柵圖形數(shù)據(jù)矢沿。這個(gè)類一般使用RGB(紅滥搭、綠、藍(lán))顏色模型捣鲸,但也可以處理灰度級(jí)圖形瑟匆。它的構(gòu)造函數(shù)很簡(jiǎn)單,如下所示:
public BufferedImage (int width, int height, int imageType)
ImageType允許我們指定要緩沖的是什么類型的圖形摄狱,比如5-位RGB脓诡、8-位RGB、灰度級(jí)等媒役。
3.8 使用VolatileImage
許多硬件平臺(tái)和它們的操作系統(tǒng)都提供基本的硬件加速支持祝谚。例如,硬件加速一般提供矩形填充功能酣衷,和利用CPU完成同一任務(wù)相比交惯,硬件加速的效率更高。由于硬件加速分離了一部分工作穿仪,允許多個(gè)工作流并發(fā)進(jìn)行席爽,從而緩解了對(duì)CPU和系統(tǒng)總線的壓力,使得應(yīng)用能夠運(yùn)行得更快啊片。利用VolatileImage可以創(chuàng)建硬件加速的圖形以及管理圖形的內(nèi)容只锻。由于它直接利用低層平臺(tái)的能力,性能的改善程度主要取決于系統(tǒng)使用的圖形適配器紫谷。VolatileImage的內(nèi)容隨時(shí)可能丟失齐饮,也即它是“不穩(wěn)定的(volatile)”捐寥。因此,在使用圖形之前祖驱,最好檢查一下它的內(nèi)容是否丟失握恳。VolatileImage有兩個(gè)能夠檢查內(nèi)容是否丟失的方法:
public abstract int validate(GraphicsConfiguration gc);
public abstract Boolean contentsLost();
每次從VolatileImage對(duì)象復(fù)制內(nèi)容或者寫入VolatileImage時(shí),應(yīng)該調(diào)用validate()方法捺僻。contentsLost()方法告訴我們乡洼,自從最后一次validate()調(diào)用之后,圖形的內(nèi)容是否丟失匕坯。
雖然VolatileImage是一個(gè)抽象類束昵,但不要從它這里派生子類。VolatileImage應(yīng)該通過 Component.createVolatileImage()或者 GraphicsConfiguration.createCompatibleVolatileImage()方法創(chuàng)建葛峻。
3.9 使用Window Blitting
進(jìn)行滾動(dòng)操作時(shí)妻怎,所有可見的內(nèi)容一般都要重畫,從而導(dǎo)致大量不必要的重畫工作泞歉。許多操作系統(tǒng)的圖形子系統(tǒng)逼侦,包括WIN32 GDI、MacOS和X/Windows腰耙,都支持Window Blitting技術(shù)榛丢。Window Blitting技術(shù)直接在屏幕緩沖區(qū)中把圖形移到新的位置,只重畫新出現(xiàn)的區(qū)域挺庞。要在Swing應(yīng)用中使用Window Blitting技術(shù)晰赞,設(shè)置方法如下:
setScrollMode(int mode);
在大多數(shù)應(yīng)用中,使用這種技術(shù)能夠提高滾動(dòng)速度选侨。只有在一種情形下掖鱼,Window Blitting會(huì)導(dǎo)致性能降低,即應(yīng)用在后臺(tái)進(jìn)行滾動(dòng)操作援制。如果是用戶在滾動(dòng)一個(gè)應(yīng)用戏挡,那么它總是在前臺(tái),無需擔(dān)心任何負(fù)面影響晨仑。