(1)盡量指定類、方法的 final 修飾符 帶有 final 修飾符的類是不可派生的,。在 Java 核心 API 中,,有許多應(yīng)用 final 的例子,例如 java.lang.String,,整個(gè)類都是 final 的,。為類指定 final 修飾符可以讓類不可以被繼承,為方法指定 final 修飾符可以讓方法不可以被重寫,。如果指定了一個(gè)類為 final,,則該類所有的方法都是 final 的。Java 編譯器會(huì)尋找機(jī)會(huì)內(nèi)聯(lián)所有的 final 方法,,內(nèi)聯(lián)對(duì)于提升 Java 運(yùn)行效率作用重大,,具體可以查閱 Java 運(yùn)行期優(yōu)化相關(guān)資料,此舉能夠使性能平均提高 50%,。 (2)盡量重用對(duì)象 特別是 String 對(duì)象的使用,,出現(xiàn)字符串連接時(shí)應(yīng)該使用 StringBuilder/StringBuffer 代替。由于 Java 虛擬機(jī)不僅要花時(shí)間生成對(duì)象,,以后可能還需要花時(shí)間對(duì)這些對(duì)象進(jìn)行垃圾回收和處理,,因此生成過多的對(duì)象將會(huì)給程序的性能帶來很大的影響。 (3)盡可能使用局部變量 調(diào)用方法時(shí)傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時(shí)變量都保存在棧中,,速度較快,,其他變量,如靜態(tài)變量、實(shí)例變量等,,都在堆中創(chuàng)建,,速度較慢。另外,,棧中創(chuàng)建的變量,,隨著方法的運(yùn)行結(jié)束,這些內(nèi)容就沒了,,不需要額外的垃圾回收,。 (4)及時(shí)關(guān)閉流。 Java 編程過程中,,進(jìn)行數(shù)據(jù)庫連接,、I/O 流操作時(shí)務(wù)必小心,在使用完畢后,,及時(shí)關(guān)閉以釋放資源,。因?yàn)閷?duì)這些大對(duì)象的操作會(huì)造成系統(tǒng)大的開銷,稍有不慎,,將會(huì)導(dǎo)致嚴(yán)重的后果,。 //性能不好,list.size() 會(huì)重復(fù)調(diào)用 (6)盡量采用懶加載的策略,即在需要的時(shí)候才創(chuàng)建,。 這個(gè)原則其實(shí)就是節(jié)約,,具體樣例如下。 //不好的示范 (7)慎用異常,。 異常對(duì)性能不利,,拋出異常首先要?jiǎng)?chuàng)建一個(gè)新的對(duì)象,Throwable 接口的構(gòu)造函數(shù)調(diào)用名為 fillInStackTrace() 的本地同步方法,,fillInStackTrace() 方法檢查堆棧,,收集調(diào)用跟蹤信息。只要有異常被拋出,,Java 虛擬機(jī)就必須調(diào)整調(diào)用堆棧,,因?yàn)樵谔幚磉^程中創(chuàng)建了一個(gè)新的對(duì)象,。異常只能用于錯(cuò)誤處理,,不應(yīng)該用來控制程序流程。 (8)不要在循環(huán)中使用 try-catch,,應(yīng)該把其放在最外層 根據(jù)網(wǎng)友們提出的意見,,這一點(diǎn)我認(rèn)為值得商榷,其實(shí)分業(yè)務(wù)場景吧,有些場景需要循環(huán)終止,,有些只是為了忽略當(dāng)此循環(huán)處理,。 (9)如果能估計(jì)到待添加的內(nèi)容長度,為底層以數(shù)組方式實(shí)現(xiàn)的集合,、工具類指定初始長度 比如 ArrayList,、LinkedLlist、StringBuilder,、StringBuffer,、HashMap、HashSet 等,,以 StringBuilder 為例,,StringBuilder() 構(gòu)造方法默認(rèn)分配 16 個(gè)字符的空間,StringBuilder(int size) 構(gòu)造方法默認(rèn)分配 size 個(gè)字符的空間,,StringBuilder(String str) 構(gòu)造方法默認(rèn)分配 16 個(gè)字符加 str.length() 個(gè)字符空間,,所以可以通過類的構(gòu)造方法來設(shè)定它的初始化容量,這樣可以明顯地提升性能,。 (10)當(dāng)復(fù)制大量數(shù)據(jù)時(shí),,使用 System.arraycopy() 命令。 這個(gè)肯定大家都沒有疑問的,,性能優(yōu)化的實(shí)現(xiàn)而已,。 (11)乘法和除法使用移位操作。 用移位操作可以極大地提高性能,,因?yàn)樵谟?jì)算機(jī)底層,,對(duì)位的操作是最方便、最快的,,但是移位操作雖然快,,可能會(huì)使代碼不太好理解,因此最好加上相應(yīng)的注釋,。 //不好的示范 (12)循環(huán)內(nèi)不要不斷創(chuàng)建對(duì)象引用,。 見如下案例解釋分析原因。 //不好的示范 (13)基于效率和類型檢查的考慮,應(yīng)該盡可能使用 array,,無法確定數(shù)組大小時(shí)才使用 ArrayList,。 (14)盡量使用 HashMap、ArrayList,、StringBuilder,,除非線程安全需要,否則不推薦使用 Hashtable,、Vector,、StringBuffer,后三者由于使用同步機(jī)制而導(dǎo)致了性能開銷,。 (15)不要將數(shù)組聲明為 public static final,。 因?yàn)檫@毫無意義,這樣只是定義了引用為 static final,,數(shù)組的內(nèi)容還是可以隨意改變的,,將數(shù)組聲明為 public 更是一個(gè)安全漏洞,這意味著這個(gè)數(shù)組可以被外部類所改變,。 (16)盡量在合適的場合使用單例,。 使用單例可以減輕加載的負(fù)擔(dān)、縮短加載的時(shí)間,、提高加載的效率,,但并不是所有地方都適用于單例,簡單來說,,單例主要適用于以下三個(gè)方面: 控制資源的使用,,通過線程同步來控制資源的并發(fā)訪問; 控制實(shí)例的產(chǎn)生,,以達(dá)到節(jié)約資源的目的,; 控制數(shù)據(jù)的共享,在不建立直接關(guān)聯(lián)的條件下,,讓多個(gè)不相關(guān)的進(jìn)程或線程之間實(shí)現(xiàn)通信,; (17)盡量避免隨意使用靜態(tài)變量。 因?yàn)楫?dāng)某個(gè)對(duì)象被定義為 static 的變量所引用,,那么 gc 通常是不會(huì)回收這個(gè)對(duì)象所占有的堆內(nèi)存的,。 public class A { (18)及時(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ì)把部分?jǐn)?shù)據(jù)轉(zhuǎn)移到磁盤,,應(yīng)用服務(wù)器也可能根據(jù)MRU(最近最頻繁使用)算法把部分不活躍的會(huì)話轉(zhuǎn)儲(chǔ)到磁盤,,甚至可能拋出內(nèi)存不足的異常。如果會(huì)話要被轉(zhuǎn)儲(chǔ)到磁盤,,那么必須要先被序列化,,在大規(guī)模集群中,對(duì)對(duì)象進(jìn)行序列化的代價(jià)是很昂貴的,。因此,,當(dāng)會(huì)話不再需要時(shí),應(yīng)當(dāng)及時(shí)調(diào)用 HttpSession 的 invalidate() 方法清除會(huì)話,。 (19)實(shí)現(xiàn) RandomAccess 接口的集合(比如 ArrayList)應(yīng)當(dāng)使用最普通的 for 循環(huán)而不是 foreach 循環(huán)來遍歷,。 這是 JDK 推薦給用戶的,JDK API 對(duì)于 RandomAccess 接口的解釋是實(shí)現(xiàn) RandomAccess 接口用來表明其支持快速隨機(jī)訪問,,此接口的主要目的是允許一般的算法更改其行為,,從而將其應(yīng)用到隨機(jī)或連續(xù)訪問列表時(shí)能提供良好的性能。實(shí)際經(jīng)驗(yàn)表明,,實(shí)現(xiàn) RandomAccess 接口的類實(shí)例,,假如是隨機(jī)訪問的,使用普通 for 循環(huán)效率將高于使用 foreach 循環(huán),,反過來,,如果是順序訪問的,則使用 Iterator 會(huì)效率更高,。 //樣板代碼:可以使用類似如下的代碼作判斷,。 (20)使用同步代碼塊替代同步方法。 盡量使用同步代碼塊,,避免對(duì)那些不需要進(jìn)行同步的代碼也進(jìn)行了同步,,影響了代碼執(zhí)行效率。 (21)將常量聲明為 static final,,并以大寫命名,。 這樣在編譯期間就可以把這些內(nèi)容放入常量池中,避免運(yùn)行期間計(jì)算生成常量的值,。另外,,將常量的名字以大寫命名也可以方便區(qū)分出常量與變量。 (22)不要?jiǎng)?chuàng)建一些不使用的對(duì)象,,不要導(dǎo)入一些不使用的類,。 這毫無意義,,如果代碼中出現(xiàn) 'The value of the local variable i is not used'、'The import java.util is never used',,那么請刪除這些無用的內(nèi)容,,雖說沒啥影響,但是有些時(shí)候編譯期會(huì)報(bào)錯(cuò),,譬如沒 import 用到的類被刪掉了,。 (23)程序運(yùn)行過程中避免使用反射。 不建議在程序運(yùn)行過程中使用,,除非萬不得已,,尤其是頻繁使用反射機(jī)制,特別是 Method 的 invoke 方法,,如果確實(shí)有必要,,一種建議性的做法是將那些需要通過反射加載的類在項(xiàng)目啟動(dòng)的時(shí)候通過反射實(shí)例化出一個(gè)對(duì)象并放入內(nèi)存,用戶只關(guān)心和對(duì)端交互的時(shí)候獲取最快的響應(yīng)速度,,并不關(guān)心對(duì)端的項(xiàng)目啟動(dòng)花多久時(shí)間,。 (24)使用數(shù)據(jù)庫連接池和線程池。 這兩個(gè)池都是用于重用對(duì)象的,,前者可以避免頻繁地打開和關(guān)閉連接,,后者可以避免頻繁地創(chuàng)建和銷毀線程。 (25)使用帶緩沖的輸入輸出流進(jìn)行 IO 操作,。 帶緩沖的輸入輸出流,,即 BufferedReader、BufferedWriter,、BufferedInputStream,、BufferedOutputStream,這可以極大地提升 IO 效率,。 (26)順序插入和隨機(jī)訪問比較多的場景使用 ArrayList,,元素刪除和中間插入比較多的場景使用 LinkedList。 (27)不要讓 public 方法中有太多的形參,。 public 方法即對(duì)外提供的方法,,如果給這些方法太多形參的話主要壞處是違反了面向?qū)ο蟮木幊趟枷耄琂ava 講求一切都是對(duì)象,,太多的形參和面向?qū)ο蟮木幊趟枷氩⒉黄鹾?,參?shù)太多勢必導(dǎo)致方法調(diào)用的出錯(cuò)概率增加。 (28)字符串變量和字符串常量 equals 的時(shí)候?qū)⒆址A繉懺谇懊?,這樣可以避免空指針,。 (29)建議使用 if (i == 1) 而不是 if (1 == i) 的方式。 因?yàn)橛锌赡?== 會(huì)誤寫成 =,,而在 C/C++ 中 if (i = 1) 是會(huì)出問題的,,而 Java 會(huì)在編譯時(shí)報(bào)錯(cuò) 'Type mismatch: cannot convert from int to boolean',,但是,盡管Java的 if (i == 1) 和 if (1 == i) 在語義上沒有任何區(qū)別,,從閱讀習(xí)慣上講,,建議使用前者會(huì)更好些。 (30)不要對(duì)數(shù)組使用 toString() 方法,。 本意是想打印出數(shù)組內(nèi)容,,卻打出來的是對(duì)象信息,,甚至有可能因?yàn)閿?shù)組引用為空而導(dǎo)致空指針異常,。對(duì)于集合 toString() 是可以打印出集合里面的內(nèi)容的,因?yàn)榧系母割?AbstractCollections (31)不要對(duì)超出范圍的基本數(shù)據(jù)類型做向下強(qiáng)制轉(zhuǎn)型,。 (32)公用的集合類中不使用的數(shù)據(jù)一定要及時(shí) remove 掉,。 如果一個(gè)集合類是公用的(也就是說不是方法里面的屬性),那么這個(gè)集合里面的元素是不會(huì)自動(dòng)釋放的,,因?yàn)槭冀K有引用指向它們,。所以,如果公用集合里面的某些數(shù)據(jù)不使用而不去remove掉它們,,那么將會(huì)造成這個(gè)公用集合不斷增大,,使得系統(tǒng)有內(nèi)存泄露的隱患。 (33)把一個(gè)基本數(shù)據(jù)類型轉(zhuǎn)為字符串,,基本數(shù)據(jù)類型.toString() 是最快的方式,、String.valueOf(數(shù)據(jù)) 次之、數(shù)據(jù)+'' 最慢,。 因?yàn)?String.valueOf() 方法底層調(diào)用了 Integer.toString() 方法,,但是會(huì)在調(diào)用前做空判斷;Integer.toString() 是直接調(diào)用,;i + '' 底層使用了 StringBuilder 實(shí)現(xiàn),,先用 append 方法拼接,再用 toString() 方法獲取字符串,。 (34)使用最有效率的方式去遍歷 Map,。 遍歷 Map 的方式有很多,通常場景下我們需要的是遍歷 Map 中的 Key 和 Value,,那么推薦使用的,、效率最高的方式是 entrySet(),如果只是想遍歷一下這個(gè) Map 的 key 值則 keySet() 會(huì)比較合適一些,。 (35)對(duì)資源的 close() 建議分開操作,。 雖然有些麻煩,,卻能避免資源泄露,這其實(shí)和 try-catch 機(jī)制相關(guān),,各自分開 close 各自的 try-catch 就會(huì)互不影響,,防止寫在一個(gè) try-catch 中因?yàn)橐粋€(gè)異常了后面的釋放不了。 (36)對(duì)于 ThreadLocal 在線程池場景使用前或者使用后一定要先 remove,。 因?yàn)榫€程池技術(shù)做的是一個(gè)線程重用,,這意味著代碼運(yùn)行過程中一條線程使用完畢并不會(huì)被銷毀而是等待下一次的使用,而 Thread 類中持有 ThreadLocal.ThreadLocalMap 的引用,,線程不銷毀意味著上條線程 set 的 ThreadLocal.ThreadLocalMap 中的數(shù)據(jù)依然存在,,那么在下一條線程重用這個(gè) Thread 的時(shí)候很可能 get 到的是上條線程 set 的數(shù)據(jù)而不是自己想要的內(nèi)容。這個(gè)問題非常隱晦,,一旦出現(xiàn)這個(gè)原因?qū)е碌腻e(cuò)誤,,沒有相關(guān)經(jīng)驗(yàn)或者沒有扎實(shí)的基礎(chǔ)非常難發(fā)現(xiàn)這個(gè)問題,因此在寫代碼的時(shí)候就要注意這一點(diǎn),,這將給你后續(xù)減少很多的工作量,。 (37)切記以常量定義的方式替代魔鬼數(shù)字,魔鬼數(shù)字的存在將極大地降低代碼可讀性,,字符串常量是否使用常量定義可以視情況而定,。 (38)long 或者 Long 初始賦值時(shí)使用大寫的 L 而不是小寫的 l,因?yàn)樽帜?l 極易與數(shù)字 1 混淆,,這個(gè)點(diǎn)非常細(xì)節(jié),,值得注意。 (39)所有重寫的方法必須保留 @Override 注解,。 這么做可以清楚地知道這個(gè)方法由父類繼承而來,,同時(shí)可以保證重寫成功,此外在抽象類中對(duì)方法簽名進(jìn)行修改,,實(shí)現(xiàn)類會(huì)馬上報(bào)出編譯錯(cuò)誤,。 (40)推薦使用 JDK7 中新引入的 Objects 工具類來進(jìn)行對(duì)象的 equals 比較,直接 a.equals(b) 有空指針異常的風(fēng)險(xiǎn),。 (41)循環(huán)體內(nèi)不要使用 '+' 進(jìn)行字符串拼接,,而直接使用 StringBuilder 不斷 append。 因?yàn)槊看翁摂M機(jī)碰到 '+' 這個(gè)操作符對(duì)字符串進(jìn)行拼接的時(shí)候會(huì) new 出一個(gè) StringBuilder,,然后調(diào)用 append 方法,,最后調(diào)用 toString() 方法轉(zhuǎn)換字符串賦值給對(duì)象,所以循環(huán)多少次,,就會(huì) new 出多少個(gè) StringBuilder() 來,,這對(duì)于內(nèi)存是一種浪費(fèi)。 (42)不捕獲 Java 類庫中定義的繼承自 RuntimeException 的運(yùn)行時(shí)異常類。 異常處理效率低,,RuntimeException 的運(yùn)行時(shí)異常中絕大多數(shù)完全可以由程序員來規(guī)避,,比如 ArithmeticException 可以通過判斷除數(shù)是否為空來規(guī)避,NullPointerException 可以通過判斷對(duì)象是否為空來規(guī)避,,IndexOutOfBoundsException 可以通過判斷數(shù)組/字符串長度來規(guī)避,,ClassCastException 可以通過 instanceof 關(guān)鍵字來規(guī)避,ConcurrentModificationException 可以使用迭代器來規(guī)避,。 (43)靜態(tài)類,、單例類、工廠類將它們的構(gòu)造函數(shù)置為 private,。 這是因?yàn)殪o態(tài)類,、單例類、工廠類這種類本來我們就不需要外部將它們 new 出來,,將構(gòu)造函數(shù)置為 private 之后,,保證了這些類不會(huì)產(chǎn)生實(shí)例對(duì)象,。 |
|