作為開發(fā)人員我們都希望編寫的程序擁有最佳的性能走诞,但是這需要大量的經(jīng)驗和知識。優(yōu)化應(yīng)用程序以獲得最佳性能并非易事沟饥。有幾個易于遵循的建議和最佳實踐可幫助創(chuàng)建性能良好的應(yīng)用程序棺亭。
1.在知道必須優(yōu)化之前不要進行優(yōu)化
這可能是最重要的性能調(diào)優(yōu)技巧之一。你應(yīng)該遵循常見的最佳實踐并嘗試有效地實現(xiàn)你的用例弟劲。但這并不意味著你應(yīng)該在證明必要之前替換任何標準庫或構(gòu)建復雜的優(yōu)化祷安。
在大多數(shù)情況下,過早優(yōu)化會占用大量時間并使代碼難以閱讀和維護兔乞。更糟糕的是汇鞭,這些優(yōu)化通常不會帶來任何好處,因為你花費了大量時間來優(yōu)化應(yīng)用程序的非關(guān)鍵部分庸追。
那么霍骄,你如何證明你需要優(yōu)化某些東西?
首先淡溯,需要定義應(yīng)用程序代碼的速度读整,例如,通過指定所有API調(diào)用的最大響應(yīng)時間或要在指定時間范圍內(nèi)導入的記錄數(shù)咱娶。完成后,可以測量應(yīng)用程序的哪些部分太慢并需要進行改進米间。當你這樣做時,你應(yīng)該看看第二個提示豺总。
2.使用Profiler查找真正的瓶頸
在按照第一個建議并確定需要改進的應(yīng)用程序部分后车伞,請問自己從哪里開始?
我們可以通過兩種方式處理此問題:
- 可以查看代碼喻喳,然后從看起來可疑的部分或認為可能會產(chǎn)生問題的部分開始另玖。
- 或者使用分析器并獲取有關(guān)代碼的每個部分的行為和性能的詳細信息。
顯而易見表伦,基于探查器的方法可以更好地理解代碼的性能影響谦去,并使自己可以專注于最關(guān)鍵的部分。如果我們曾經(jīng)使用過探查器蹦哼,我們會記得在一些情況下鳄哭,對代碼的哪些部分產(chǎn)生了性能問題感到驚訝。
3.為整個應(yīng)用程序創(chuàng)建性能測試套件
這是另一個通用提示纲熏,可幫助你避免在將性能改進部署到生產(chǎn)后經(jīng)常發(fā)生的許多意外問題妆丘。你應(yīng)該始終定義一個性能測試套件來測試整個應(yīng)用程序,并在你進行性能改進之前和之后運行它局劲。
這些額外的測試運行將幫助你識別更改的功能和性能副作用勺拣,并確保你不會發(fā)送造成弊大于利的更新。如果你處理應(yīng)用程序的多個不同部分(如數(shù)據(jù)庫或緩存)使用的組件鱼填,這一點尤為重要药有。
4.首先解決最大的瓶頸問題
在創(chuàng)建測試套件并使用分析器分析應(yīng)用程序之后,你將獲得要解決的問題列表以提高性能。這很好愤惰,但它仍然沒有回答你應(yīng)該從哪里開始的問題苇经。你可以專注于快速獲勝,或從最重要的問題開始宦言。
從快速獲勝開始可能很誘人扇单,因為你很快就能展示出第一批結(jié)果。有時蜡励,可能有必要說服其他團隊成員或你的管理層令花,性能分析值得付出努力。
但總的來說凉倚,我建議從頂部開始,首先開始解決最重要的性能問題嫂沉。這將為你提供最大的性能提升稽寒,你可能不需要解決多個這些問題以滿足你的性能要求。
足夠的一般性能調(diào)整技巧趟章。讓我們仔細看看一些特定于Java的杏糙。
5.使用StringBuilder以編程方式連接字符串
在Java中連接String有很多不同的選項。例如蚓土,你可以使用簡單的+或+ =宏侍,舊的StringBuffer或StringBuilder。
那么蜀漆,你更喜歡哪種方法谅河?
答案取決于連接String的代碼。如果你以編程方式向String添加新內(nèi)容确丢,例如绷耍,在for循環(huán)中,則應(yīng)使用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 = new StringBuilder(“This is a test”);
for (int i=0; i<10; i++) {
sb.append(i);
sb.append(” “);
}
log.info(sb.toString());
正如你在代碼片段中看到的骂租,你可以將String的第一個元素提供給構(gòu)造函數(shù)方法祷杈。這將創(chuàng)建一個新的StringBuilder,其中包含提供的String和16個附加字符的容量渗饮。當你向StringBuilder添加更多字符時但汞,你的JVM將動態(tài)增加StringBuilder的大小。
如果你已經(jīng)知道String將包含多少個字符互站,則可以將該數(shù)字提供給不同的構(gòu)造函數(shù)方法私蕾,以實例化具有已定義容量的StringBuilder。這進一步提高了效率胡桃,因為它不需要動態(tài)擴展其容量踩叭。
6.使用+在一個語句中連接字符串
當你使用Java實現(xiàn)第一個應(yīng)用程序時,有人可能會告訴你不應(yīng)該使用+連接String。如果你在應(yīng)用程序邏輯中連接String,這是正確的淹接。字符串是不可變的,每個字符串連接的結(jié)果都存儲在一個新的String對象中斤富。這需要額外的內(nèi)存并減慢你的應(yīng)用程序,特別是如果你在循環(huán)中連接多個String锻狗。
在這些情況下满力,你應(yīng)該遵循5號提示并使用StringBuilder。
但是屋谭,如果你只是將String分成多行來提高代碼的可讀性脚囊,情況并非如此。
Query q = em.createQuery(“SELECT a.id, a.firstName, a.lastName ”
+ “FROM Author a ”
+ “WHERE a.id = :id”);
在這些情況下桐磁,你應(yīng)該將String與一個簡單的+連接起來悔耘。你的Java編譯器將對此進行優(yōu)化并在編譯時執(zhí)行串聯(lián)。因此我擂,在運行時衬以,你的代碼將只使用1個字符串,并且不需要連接校摩。
7.盡可能使用基元
另一種避免任何開銷和提高應(yīng)用程序性能的快捷方法是使用原始類型而不是它們的包裝類看峻。因此,最好使用int而不是Integer衙吩,或者使用double而不是Double互妓。這使你的JVM來 的值存儲在堆棧,而不是堆的,以減少內(nèi)存消耗和整體更有效地處理它冯勉。
8.盡量避免使用BigInteger和BigDecimal
由于我們已經(jīng)在談?wù)摂?shù)據(jù)類型澈蚌,我們還應(yīng)該快速瀏覽一下BigInteger和BigDecimal。特別是后者因其精確性而受歡迎灼狰。但這需要付出代價宛瞄。
BigInteger和BigDecimal需要比簡單的long或double更多的內(nèi)存,并且顯著減慢所有計算速度交胚。因此份汗,如果你需要額外的精度,或者如果你的數(shù)字將超過長的范圍蝴簇,最好三思而后行杯活。這可能是你需要更改以修復性能問題的唯一方法,尤其是在你實施數(shù)學算法時熬词。
9.首先檢查當前日志級別
這個建議應(yīng)該是顯而易見的轩猩,但不幸的是,你可以找到許多忽略它的代碼荡澎。在創(chuàng)建調(diào)試消息之前,應(yīng)始終先檢查當前日志級別晤锹。否則摩幔,你可能會創(chuàng)建一個包含 日志消息的String,之后將被忽略鞭铆。
以下是你不應(yīng)該這樣做的兩個示例或衡。
// 不能這樣寫
log.debug(“User [” + userName + “] called method X with [” + i + “]”);
//也不能這樣寫
log.debug(String.format(“User [%s] called method X with [%d]”, userName, i));
在這兩種情況下,你將執(zhí)行所有必需的步驟來創(chuàng)建日志消息车遂,而無需知道你的日志記錄框架是否將使用日志消息封断。在創(chuàng)建調(diào)試消息之前,最好先檢查當前日志級別舶担。
if (log.isDebugEnabled()) {
log.debug(“User [” + userName + “] called method X with [” + i + “]”);
}
10.使用Apache Commons StringUtils.Replace而不是String.replace
通常坡疼,String.replace方法工作正常并且效率很高,特別是如果你使用的是Java 9.但是如果你的應(yīng)用程序需要大量的替換操作并且你還沒有更新到最新的Java版本衣陶,那么它仍然有意義檢查更快柄瑰,更有效的替代品。
一個候選人是 Apache Commons Lang的StringUtils.replace方法剪况。正如Lukas Eder在 他最近的一篇博客文章中所描述的那樣教沾,它顯著優(yōu)于Java 8的String.replace方法。
它只需要一個微小的變化译断。你需要將Apache的Commons Lang項目的Maven依賴項添加到應(yīng)用程序pom.xml中授翻,并使用StringUtils.replace方法替換String.replace方法的所有調(diào)用。
// 這個寫法換成下面的寫法
test.replace(“test”, “simple test”);
// 替換寫法
StringUtils.replace(test, “test”, “simple test”);
11.緩存昂貴的資源,就像數(shù)據(jù)庫連接一樣
緩存是一種流行的解決方案堪唐,可以避免重復執(zhí)行昂貴或經(jīng)常使用的代碼片段巡语。一般的想法很簡單:重復使用這些資源比一次又一次地創(chuàng)建新資源要便宜。
典型示例是緩存池中的數(shù)據(jù)庫連接羔杨。創(chuàng)建新連接需要時間捌臊,如果重用現(xiàn)有連接,則可以避免這種情況兜材。
你還可以在Java語言本身中找到其他示例理澎。例如,Integer類的valueOf方法將值緩存在-128和127之間曙寡。你可能會說新的Integer的創(chuàng)建不是太昂貴糠爬,但是經(jīng)常使用它來緩存最常用的值提供性能優(yōu)勢。
但是當你考慮緩存時举庶,請記住你的緩存實現(xiàn)也會產(chǎn)生開銷执隧。你需要花費額外的內(nèi)存來存儲可重用資源,并且可能需要管理緩存以使資源可訪問或刪除過時的資源户侥。
因此镀琉,在開始緩存任何資源之前,請確保經(jīng)常使用它們來超過緩存實現(xiàn)的開銷蕊唐。