大多數(shù)開發(fā)人員理所當然地以為性能優(yōu)化很復雜驶鹉,需要大量的經(jīng)驗和知識绩蜻。好吧,不能說這是完全錯誤的室埋。優(yōu)化應用程序以獲得最佳性能不是一件容易的事情办绝。但是,這并不意味著如果你不具備這些知識姚淆,就不能做任何事情孕蝉。這里有11個易于遵循的建議和最佳實踐可以幫助你創(chuàng)建一個性能良好的應用程序。
大部分建議是針對Java的腌逢。但也有若干建議是與語言無關(guān)的降淮,可以應用于所有應用程序和編程語言涕蜂。在討論專門針對Java的性能調(diào)優(yōu)技巧之前勺鸦,讓我們先來看看通用技巧船庇。
1.在你知道必要之前不要優(yōu)化
這可能是最重要的性能調(diào)整技巧之一坤塞。你應該遵循常見的最佳實踐做法并嘗試高效地實現(xiàn)用例。但是谒兄,這并不意味著在你證明必要之前笑诅,你應該更換任何標準庫或構(gòu)建復雜的優(yōu)化靠益。
在大多數(shù)情況下妒蔚,過早優(yōu)化不但會占用大量時間穿挨,而且會使代碼變得難以閱讀和維護。更糟糕的是肴盏,這些優(yōu)化通常不會帶來任何好處科盛,因為你花費大量時間來優(yōu)化的是應用程序的非關(guān)鍵部分。
那么菜皂,你如何證明你需要優(yōu)化一些東西呢土涝?
首先,你需要定義應用程序代碼的速度得多快幌墓,例如但壮,為所有API調(diào)用指定最大響應時間,或者指定在特定時間范圍內(nèi)要導入的記錄數(shù)量常侣。在完成這些之后蜡饵,你就可以測量應用程序的哪些部分太慢需要改進。然后胳施,接著看第二個技巧溯祸。
2.使用分析器查找真正的瓶頸
在你遵循第一個建議并確定了應用程序的某些部分需要改進后,那么從哪里開始呢舞肆?
你可以用兩種方法來解決問題:
查看你的代碼焦辅,并從看起來可疑或者你覺得可能會產(chǎn)生問題的部分開始。
或者使用分析器并獲取有關(guān)代碼每個部分的行為和性能的詳細信息椿胯。
希望不需要我解釋為什么應該始終遵循第二種方法的原因筷登。
很明顯,基于分析器的方法可以讓你更好地理解代碼的性能影響哩盲,并使你能夠?qū)W⒂谧铌P(guān)鍵的部分前方。如果你曾使用過分析器,那么你一定記得曾經(jīng)你是多么驚訝于一下就找到了代碼的哪些部分產(chǎn)生了性能問題廉油。老實說惠险,我第一次的猜測不止一次地導致我走錯了方向。
3.為整個應用程序創(chuàng)建性能測試套件
這是另一個通用技巧抒线,可以幫助你避免在將性能改進部署到生產(chǎn)后經(jīng)常會發(fā)生的許多意外問題班巩。你應該總是定義一個測試整個應用程序的性能測試套件,并在性能改進之前和之后運行它嘶炭。
這些額外的測試運行將幫助你識別更改的功能和性能副作用抱慌,并確保不會導致弊大于利的更新。如果你工作于被應用程序若干不同部分使用的組件旱物,如數(shù)據(jù)庫或緩存遥缕,那么這一點就尤其重要。
4.首先處理最大的瓶頸
在創(chuàng)建測試套件并使用分析器分析應用程序之后宵呛,你可以列出一系列需要解決以提高性能的問題单匣。這很好,但它仍然不能回答你應該從哪里開始的問題宝穗。你可以專注于速效方案户秤,或從最重要的問題開始。
速效方案一開始可能會很有吸引力逮矛,因為你可以很快顯示第一個成果鸡号。但有時,可能需要你說服其他團隊成員或管理層認為性能分析是值得的——因為暫時看不到效果须鼎。
但總的來說鲸伴,我建議首先處理最重要的性能問題府蔗。這將為你提供最大的性能改進,而且可能再也不需要去解決其中一些為了滿足性能需求的問題汞窗。
常見的性能調(diào)整技巧到此結(jié)束姓赤。下面讓我們仔細看看一些特定于Java的技巧。
5.使用StringBuilder以編程方式連接String
有很多不同的選項來連接Java中的String仲吏。例如不铆,你可以使用簡單的+或+ =,以及StringBuffer或StringBuilder裹唆。
那么誓斥,你應該選擇哪種方法?
答案取決于連接String的代碼许帐。如果你是以編程方式添加新內(nèi)容到String中劳坑,例如在for循環(huán)中,那么你應該使用StringBuilder舞吭。它很容易使用泡垃,并提供比StringBuffer更好的性能。但請記住羡鸥,與StringBuffer相比蔑穴,StringBuilder不是線程安全的,可能不適合所有用例惧浴。
你只需要實例化一個新的StringBuilder并調(diào)用append方法來向String中添加一個新的部分存和。在你添加了所有的部分之后,你就可以調(diào)用toString()方法來檢索連接的String衷旅。
下面的代碼片段顯示了一個簡單的例子捐腿。在每次迭代期間,這個循環(huán)將i轉(zhuǎn)換為一個String柿顶,并將它與一個空格一起添加到StringBuilder sb中茄袖。所以,最后嘁锯,這段代碼將在日志文件中寫入“This is a test0 1 2 3 4 5 6 7 8 9”宪祥。
StringBuilder sb =newStringBuilder(“Thisisatest”);for(inti=0;?i<10;?i++)?{sb.append(i);sb.append(”?“);}log.info(sb.toString());
正如在代碼片段中看到的那樣,你可以將String的第一個元素提供給構(gòu)造方法家乘。這將創(chuàng)建一個新的StringBuilder蝗羊,新的StringBuilder包含提供的String和16個額外字符的容量。當你向StringBuilder添加更多字符時仁锯,JVM將動態(tài)增加StringBuilder的大小耀找。
如果你已經(jīng)知道你的String將包含多少個字符,則可以將該數(shù)字提供給不同的構(gòu)造方法以實例化具有定義容量的StringBuilder业崖。這進一步提高了效率野芒,因為它不需要動態(tài)擴展其容量蓄愁。
6.使用+連接一個語句中的String
當你用Java實現(xiàn)你的第一個應用程序時,可能有人告訴過你不應該用+來連接String狞悲。如果你是在應用程序邏輯中連接字符串涝登,這是正確的。字符串是不可變的效诅,每個字符串的連接結(jié)果都存儲在一個新的String對象中。這需要額外的內(nèi)存趟济,會減慢你的應用程序乱投,特別是如果你在一個循環(huán)內(nèi)連接多個字符串的話。
在這些情況下顷编,你應該遵循技巧5并使用StringBuilder戚炫。
但是,如果你只是將字符串分成多行來改善代碼的可讀性媳纬,那情況就不一樣了双肤。
Queryq = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”+ “FROM Author a ”+ “WHERE a.id = :id”);
在這些情況下,你應該用一個簡單的+來連接你的字符串钮惠。Java編譯器會對此優(yōu)化并在編譯時執(zhí)行連接茅糜。所以,在運行時素挽,你的代碼將只使用1個String蔑赘,不需要連接。
7.盡可能使用基元
避免任何開銷并提高應用程序性能的另一個簡便而快速的方法是使用基本類型而不是其包裝類预明。所以缩赛,最好使用int來代替Integer,使用double來代替Double撰糠。這允許JVM將值存儲在堆棧而不是堆中以減少內(nèi)存消耗酥馍,并作出更有效的處理。
8.試著避免BigInteger和BigDecimal
既然我們在討論數(shù)據(jù)類型阅酪,那么我們也快速瀏覽一下BigInteger和BigDecimal吧旨袒。尤其是后者因其精確性而受到大家的歡迎。但是這是有代價的遮斥。
BigInteger和BigDecimal比簡單的long或double需要更多的內(nèi)存峦失,并且會顯著減慢所有計算。所以术吗,你如果需要額外的精度尉辑,或者數(shù)字將超過long的范圍,那么最好三思而后行较屿。這可能是你需要更改以解決性能問題的唯一方法隧魄,特別是在實現(xiàn)數(shù)學算法的時候卓练。
9.首先檢查當前日志級別
這個建議應該是顯而易見的,但不幸的是购啄,很多程序員在寫代碼的時候都會大多會忽略它襟企。在你創(chuàng)建調(diào)試消息之前,始終應該首先檢查當前日志級別狮含。否則顽悼,你可能會創(chuàng)建一個之后會被忽略的日志消息字符串。
這里有兩個反面例子几迄。
// don’tdothislog.debug(“User[” + userName + “] called method Xwith[” + i + “]”);// or thislog.debug(String.format(“User [%s] called method Xwith[%d]”, userName, i));
在上面兩種情況中蔚龙,你都將執(zhí)行創(chuàng)建日志消息所有必需的步驟,在不知道日志框架是否將使用日志消息的前提下映胁。因此在創(chuàng)建調(diào)試消息之前木羹,最好先檢查當前的日志級別。
//dothisif (log.isDebugEnabled()) { log.debug(“User[” + userName + “] called method Xwith[” + i + “]”);}
10.使用Apache Commons StringUtils.Replace而不是String.replace
一般來說解孙,String.replace方法工作正常坑填,效率很高,尤其是在使用Java 9的情況下弛姜。但是脐瑰,如果你的應用程序需要大量的替換操作,并且沒有更新到最新的Java版本娱据,那么我們依然有必要查找更快和更有效的替代品蚪黑。
有一個備選答案是Apache Commons Lang的StringUtils.replace方法。正如Lukas Eder在他最近的一篇博客文章中所描述的中剩,StringUtils.replace方法遠勝Java 8的String.replace方法忌穿。
而且它只需要很小的改動。即添加Apache Commons Lang項目的Maven依賴項到應用程序pom.xml中结啼,并將String.replace方法的所有調(diào)用替換為StringUtils.replace方法掠剑。
//replacethistest.replace(“test”, “simpletest”);//withthisStringUtils.replace(test, “test”, “simpletest”);
11.緩存昂貴的資源,如數(shù)據(jù)庫連接
緩存是避免重復執(zhí)行昂貴或常用代碼片段的流行解決方案郊愧∑右耄總的思路很簡單:重復使用這些資源比反復創(chuàng)建新的資源要便宜。
一個典型的例子是緩存池中的數(shù)據(jù)庫連接属铁。新連接的創(chuàng)建需要時間眠寿,如果你重用現(xiàn)有連接,則可以避免這種情況焦蘑。
你還可以在Java語言本身找到其他例子盯拱。例如,Integer類的valueOf方法緩存了-128到127之間的值。你可能會說創(chuàng)建一個新的Integer并不是太昂貴狡逢,但是由于它經(jīng)常被使用宁舰,以至于緩存最常用的值也可以提供性能優(yōu)勢。
但是奢浑,當你考慮緩存時蛮艰,請記住緩存實現(xiàn)也會產(chǎn)生開銷。你需要花費額外的內(nèi)存來存儲可重用資源雀彼,因此你可能需要管理緩存以使資源可訪問壤蚜,以及刪除過時的資源。
所以徊哑,在開始緩存任何資源之前仍律,請確保實施緩存是值得的,也就是說必須足夠多地使用它們实柠。
總結(jié)
正如你所看到的,有時不需要太多工作就可以提高應用程序的性能善涨。本文中的大部分建議只需要你稍作努力就可以將它們應用于你的代碼窒盐。
但是:
1,在你知道必要之前不要優(yōu)化
2钢拧,使用分析器查找真正的瓶頸
3蟹漓,首先處理最大的瓶頸
轉(zhuǎn)載自:互聯(lián)網(wǎng)