11個值得掌握的Java代碼性能優(yōu)化技巧
1.避免方法過長
我們在定義一個方式的時候,應(yīng)該考慮到一個方法不應(yīng)該太長,它就應(yīng)該是專門是來執(zhí)行單一功能的。這樣其實(shí)對維護(hù)和性能都有好處。
一方面,從維護(hù)角度來說,適當(dāng)長度的方法易讀性更強(qiáng),更容易理解;另一方面,在類的加載和方法調(diào)用的過程中,方法會被加載到內(nèi)存中。如果一個方法太大,處理起來就需要消耗額外的內(nèi)存和CPU周期。我們應(yīng)該學(xué)會在恰當(dāng)?shù)倪壿孅c(diǎn)上將一個長方法拆開。
2.避免多個if-else語句
對于這個優(yōu)化點(diǎn),大家應(yīng)該很熟悉了。但是實(shí)際在寫代碼的時候,還是if-else一擼到底。
這樣做的話,其實(shí)也會影響性能。因?yàn)镴VM必須對條件進(jìn)行比較。如果在for、while等循環(huán)語句中使用同樣的條件,情況會變得更糟糕。
如果我們的業(yè)務(wù)邏輯中有很多的條件,我們可以嘗試著將這些條件分組并且返回一個布爾值,然后再將其用于if語句。
另外,如果可能的話,我們可以考慮使用switch語句來代替多個if-else。switch語句比if-else有性能優(yōu)勢。 下面我們看一個例子:
if (condition1) {
if (condition2) {
if (condition3 || condition4) { execute ..}
else { execute..}
對比上面這段代碼,合適的做法應(yīng)該如下:
boolean result = (condition1 && condition2) && (condition3 || condition4)
3.避免使用iterator
用Java5的foreach風(fēng)格來寫循環(huán)確實(shí)很方便很簡潔,看起來就很酷!
但是有的時候??崾且冻鲂阅艿拇鷥r的。
例如:
for (String str: strs) {
. . .
}
每次運(yùn)行代碼,如果strs是Iterable的,你將會創(chuàng)建一個新的Iterator對象。這樣做會導(dǎo)致更多內(nèi)存的消耗。
如果你對性能有著極致的追求,那么還是建議你使用原始的寫法:
int size = strs.size();
for (int i = 0; i < size; i++) {
String value = strs.get(i);
. . .
}
4. 避免在集合中獲取size
在對任何集合進(jìn)行迭代時,要事先得到集合的大小,而不是在迭代過程中得到它——這樣避免多次調(diào)用size()方法。
下面請看這個例子:
List<String> eleList = getData();
for (int i = 0; i < eleList.size(); i++) { execute code ..}
對比上面這段代碼,合適的做法應(yīng)該如下:
List<String> objList = getData();
int size = objList.size();
for (int i = 0; i < size; i++) { execute code ..}
5.避免使用+號拼接字符串
從JDK5開始,Java編譯器就做了優(yōu)化,使用+號拼接字符串,編譯器編譯后實(shí)際就自動優(yōu)化為使用StringBuilder。
而且String是final類,用String創(chuàng)建的對象無法重復(fù)使用。因此,如果我們需要連續(xù)拼接,使用+號拼接字符串將導(dǎo)致創(chuàng)建多個String對象,從而會占用更多的堆內(nèi)存。
一般來說,當(dāng)字符串不多的時候,+號與StringBuilder的拼接效率其實(shí)相差無幾;但是如果涉及到單線程循環(huán)拼接的時候,我們最好還是使用StringBuilder以保證性能上的優(yōu)化。
下面請看一個例子:
String str = "sample";
for (int i = 0; i < count; i++) {
str = str + "-" + i;
}
更合適的做法如下:
StringBuilder stringBuilder = new StringBuilder("sample");
for (int i = 0; i < count; i++) {
stringBuilder.append("-");
stringBuilder.append(i);
}
6.盡可能使用基本類型
因?yàn)榛绢愋痛鎯υ跅?nèi)存中,而對象存儲在堆內(nèi)存中。如果可以的話,我們應(yīng)該盡可能使用基本類型而非對象,因?yàn)闂?nèi)存的訪問速度比堆內(nèi)存快。
因此在某些情況下,定義一個變量或者數(shù)組,我們可以使用int而非Integer,double而非Double。
7.避免使用BigDecimal類
BigDecimal類提供了精確的小數(shù)值,過度使用這個對象會對性能造成影響,特別是當(dāng)這個對象被用來在循環(huán)中計(jì)算某些數(shù)值時。
BigDecimal在進(jìn)行計(jì)算時要比long或double占用更多的內(nèi)存。如果精度不受限制,或者我們確認(rèn)計(jì)算值的范圍不會超過long或double,我們可以避免使用BigDecimal,而使用long或double,并進(jìn)行適當(dāng)?shù)霓D(zhuǎn)換。
8.避免經(jīng)常創(chuàng)建“代價昂貴”的對象
有一些類在應(yīng)用程序中承載著數(shù)據(jù),這些對象的創(chuàng)建開銷很大,我們應(yīng)該避免多次創(chuàng)建。
比如說,數(shù)據(jù)庫連接對象,系統(tǒng)配置對象,或者是用戶登錄的會話對象。這些對象在創(chuàng)建的時候占用了大量資源,我們應(yīng)該選擇重用這些對象,而不是再次創(chuàng)建。
對于這些"代價昂貴"的對象,我們盡可能使用單例模式來創(chuàng)建單一實(shí)例,并在需要的地方重用它。
9.使用PreparedStatement而不是Statement
現(xiàn)在應(yīng)該比較少用JDBC API進(jìn)行SQL查詢了,但是我覺得還是有必要了解一下。
對于參數(shù)化查詢,PreparedStatement比Statement更有優(yōu)勢,因?yàn)镻reparedStatement對象被編譯一次并執(zhí)行多次。Statement對象在每次被調(diào)用時都會被編譯和執(zhí)行。
此外,PreparedStatement對象是安全的,可以避免SQL注入攻擊。
10.避免使用不必要的日志語句和不正確的日志級別
這個建議應(yīng)該是很普遍的,但是很多代碼忽略了這一點(diǎn)。我們在創(chuàng)建調(diào)試信息的時候,應(yīng)該先檢查一下當(dāng)前的日志級別。
否則你可能會無意之間創(chuàng)建一條無用的日志信息。 請看例子:
log.debug("User [" + userName + "] called method X with [" + i + "]");
log.debug(String.format("User [%s] called method X with [%d]", userName, i));
11.選擇SQL查詢中的必要字段
有時,我們需要寫SQL來獲取數(shù)據(jù)。此時我們應(yīng)該避免選擇所有數(shù)據(jù)庫列,只選擇我們需要的數(shù)據(jù)庫列。
選擇太多的列會導(dǎo)致數(shù)據(jù)庫查詢執(zhí)行的延遲,也會增加網(wǎng)絡(luò)流量。
請看示例:
select * from books where book_id = 6;
對此,我建議這么寫:
select book_title, book_desc, book_price from books where book_id = 6;
結(jié)語
很多人認(rèn)為性能優(yōu)化是一個復(fù)雜的話題,需要大量的經(jīng)驗(yàn)和知識,這在一定程度上是對的。
我們開發(fā)一個應(yīng)用程序并且期望獲得盡可能好的性能并不是一件容易的事情。但是,即使你不是性能調(diào)優(yōu)專家,也可以采取一些簡單的方法來提高性能。