???? 某一個Java前輩曾這樣理解過Java代碼優(yōu)化:
?????就像鯨魚吃蝦米一樣拜轨,也許吃一個兩個蝦米對于鯨魚來說作用不大,但是吃的蝦米多了,鯨魚自然飽了悼瓮。 代碼優(yōu)化一樣埋市,也許一個兩個的優(yōu)化套么,對于提升代碼的運行效率意義不大玷室,但是只要處處都能注意代碼優(yōu)化,總體來說對于提升代碼的運行效率就很有用了身坐。
??? 這個說法涯鲁,雖然很有道理吮便,但也不全對。因為在這個硬件發(fā)達的今天咧擂,服務器內核日漸增大到了16核,CPU也增大至64位檀蹋,關于代碼執(zhí)行效率已經(jīng)是非常高了松申,即使我們不注重日常擼代碼的優(yōu)化小常識,對代碼執(zhí)行效率影響也是微乎其微俯逾。
??? 本文的內容有些來自于網(wǎng)絡前輩的領悟贸桶,有些來源于平時的工作學習,當然這些都不重要桌肴,重要的是這些代碼優(yōu)化細節(jié)是否真正的對我們有用皇筛。
代碼優(yōu)化部分細節(jié):
????(1).盡量使用final修飾類,方法名坠七。
????????? 帶有final的修飾符是不可派生的水醋,也就是被final修飾的類不可被繼承旗笔,被final修飾的方法不可被重寫。這些基礎知識大家都早已爛熟于心了拄踪,但為什么用到final修飾會提高程序性能呢蝇恶????????
?????????標準答案:(1).把方法鎖定,防止任何繼承類修改它的意義和實現(xiàn) (2).編譯器在遇到調用final方法時候會轉入內嵌機制惶桐,大大提高執(zhí)行效率艘包。
????????? 非正式解析:使用了final修飾目的是為了阻止改變和提高效率,阻止改變大家都看出來了耀盗,提高效率卻還是一頭霧水想虎。簡單說高效的原因:Final方法會在編譯過程中,被java內嵌機制進行inline優(yōu)化叛拷。
????????? PS: inline優(yōu)化就是指:在編譯時舌厨,直接調用被final修飾的方法代碼替換,也就是內嵌忿薇,而不是在代碼運行時期再去調用裙椭。inline需要在編譯時期就知道需要調用那個方法,所以就要求方法是final修飾的署浩。因為非final方法會存在被子類重寫現(xiàn)象即多態(tài)揉燃,則編譯器在編譯階段就無法確定將來調用的方法真正類型,也就無法確定調用哪一個方法筋栋。
???(2).及時關閉I/O流炊汤,數(shù)據(jù)連接
??????在日常擼代碼時候,若遇到各種流操作弊攘,或者數(shù)據(jù)庫連接操作時抢腐,應該時刻注意關閉連接。因為這些大對象的操作未及時關閉襟交,會對系統(tǒng)造成大筆開銷迈倍。日常中常見災難就是,各種上線的系統(tǒng)捣域,莫名服務器宕機啼染,其中部分原因,就是我們未及時關閉連接焕梅,從而造成的內存泄漏導致迹鹅。
??? ?(3).盡量使用懶加載模式
?????? 在程序真正需要調用時候才去new對象。
??
????(4).盡可能的使用局部變量
???????首先我們該知道丘侠,日常擼代碼會經(jīng)常用到java內存的那些區(qū)域:1.靜態(tài)存儲區(qū):主要存放靜態(tài)數(shù)據(jù)徒欣、常量逐样,程序結束后由系統(tǒng)釋放?蜗字。2.棧區(qū):當方法執(zhí)行時打肝,方法體內的局部變量,和參數(shù)在此區(qū)創(chuàng)建挪捕。編譯器自動分配釋放粗梭。 3.堆區(qū):動態(tài)內存的分配,通常指對象的實例级零,一般由程序員分配釋放断医,若程序員不釋放,程序結束時可能由GC回收奏纪。
????????調用方法時的參數(shù)以及方法里需要用到的局部變量會臨時存放在棧區(qū)鉴嗤,運行速度相比較快。而且該變量會隨著方法的結束而釋放序调,不用額外GC回收
?????(5).盡量重用對象
?????????特別是對于String對象使用醉锅,出現(xiàn)字符串鏈接時盡量使用StringBuffer/StringBuilder代替,因為java虛擬機不僅需要花時間生成對象发绢,也需要花時間進行GC硬耍。所以生成過多的對象會給程序性能帶來挺大的影響
?????(6).盡量減少對變量的重復計算
???????? 明確一個概念,只要是對方法的調用边酒,里面即使只有一句語句都是有消耗的经柴,所以如下列:
??? ??(7).慎用異常
???????異常對性能不利。拋出異常首先要創(chuàng)建一個新的對象墩朦,Throwable接口的構造函數(shù)調用名為fillInStackTrace()的本地同步方法坯认,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息氓涣。只要有異常被拋出鹃操,Java虛擬機就必須調整調用堆棧,因為在處理過程中創(chuàng)建了一個新的對象春哨。異常只能用于錯誤處理荆隘,不應該用來控制程序流程。
??????(8).如果能估計到待添加字節(jié)的使用長度赴背,為底層是以數(shù)組方式實現(xiàn)的集合椰拒、工具類初始化長度
比如ArrayList、LinkedLlist凰荚、StringBuilder燃观、StringBuffer、HashMap便瑟、HashSet等等缆毁,以StringBuilder為例:
StringBuilder() // 默認分配16個字符的空間
StringBuilder(int size) // 默認分配size個字符的空間
StringBuilder(String str) // 默認分配16個字符+str.length()個字符空間
可以通過類(這里指的不僅僅是上面的StringBuilder)的構造函數(shù)來設定它的初始化容量,這樣可以明顯地提升性能到涂。比如StringBuilder吧脊框,length表示當前的StringBuilder能保持的字符數(shù)量颁督。因為當StringBuilder達到最大容量的時候,它會將自身容量增加到當前的2倍再加2浇雹,無論何時只要StringBuilder達到它的最大容量沉御,它就不得不創(chuàng)建一個新的字符數(shù)組然后將舊的字符數(shù)組內容拷貝到新字符數(shù)組中----這是十分耗費性能的一個操作。試想昭灵,如果能預估到字符數(shù)組中大概要存放5000個字符而不指定長度吠裆,最接近5000的2次冪是4096,每次擴容加的2不管烂完,那么:
在4096 的基礎上试疙,再申請8194個大小的字符數(shù)組,加起來相當于一次申請了12290個大小的字符數(shù)組抠蚣,如果一開始能指定5000個大小的字符數(shù)組效斑,就節(jié)省了一倍以上的空間
把原來的4096個字符拷貝到新的的字符數(shù)組中去
這樣,既浪費內存空間又降低代碼運行效率柱徙。所以缓屠,給底層以數(shù)組實現(xiàn)的集合、工具類設置一個合理的初始化容量是錯不了的护侮,這會帶來立竿見影的效果敌完。但是,注意羊初,像HashMap這種是以數(shù)組+鏈表實現(xiàn)的集合滨溉,別把初始大小和你估計的大小設置得一樣,因為一個table上只連接一個對象的可能性幾乎為0长赞。初始大小建議設置為2的N次冪晦攒,如果能估計到有2000個元素,設置成new HashMap(128)得哆、new HashMap(256)都可以脯颜。
????? ?(8).當復制大量數(shù)組數(shù)據(jù)時,使用System.arraycop()命令替換使用For each()
??????? System中提供了一個native靜態(tài)方法arraycopy(),可以使用這個方法來實現(xiàn)數(shù)組之間的復制贩据。對于一維數(shù)組來說栋操,這種復制屬性值傳遞,修改副本不會影響原來的值饱亮。對于二維或者一維數(shù)組中存放的是對象時矾芙,復制結果是一維的引用變量傳遞給副本的一維數(shù)組,修改副本時近上,會影響原來的數(shù)組剔宪。
public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
代碼解釋:
Object src : 原數(shù)組
?????? int srcPos : 從元數(shù)據(jù)的起始位置開始
Object dest : 目標數(shù)組
int destPos : 目標數(shù)組的開始起始位置
int length : 要copy的數(shù)組的長度
??????(9).使用乘法和除法時用移位運算
??????(10).避免在循環(huán)體內new對象
??????? (11).盡量避免隨意使用靜態(tài)變量
要知道,當某個對象被定義為static的變量所引用,那么gc通常是不會回收這個對象所占有的堆內存的葱绒。
此時靜態(tài)變量b的生命周期與A類相同感帅,如果A類不被卸載,那么引用B指向的B對象會常駐內存哈街,直到程序終止
??????????(12).實現(xiàn)RandomAccess接口的集合比如ArrayList,應當使用for循環(huán)留瞳,而不是foreach循環(huán)
在對List特別的遍歷算法中拒迅,要盡量來判斷是屬于RandomAccess(如ArrayList)還是SequenceAccess(如LinkedList)骚秦,因為適合RandomAccess List的遍歷算法,用在SequenceAccess List上就差別很大璧微,即對于實現(xiàn)了RandomAccess接口的類實例而言作箍,此循環(huán)
for (int i=0, i<list.size(); i++) list.get(i);的運行速度要快于以下循環(huán):
for (Iterator i=list.iterator(); i.hasNext(); ) i.next();實際經(jīng)驗表明,實現(xiàn)RandomAccess接口的類實例前硫,假如是隨機訪問的胞得,使用普通for循環(huán)效率將高于使用foreach循環(huán);反過來屹电,如果是順序訪問的阶剑,則使用Iterator會效率更高
遍歷Map的方式有很多,通常場景下我們需要的是遍歷Map中的Key和Value危号,那么推薦使用的牧愁、效率最高的方式Iterator
????? (13).將常量聲明為 static fianl ,并以大寫命名
這樣在編譯期間就可以把這些內容放入常量池中,避免運行期間計算生成常量的值外莲。另外猪半,將常量的名字以大寫命名也可以方便區(qū)分出常量與變量
???????(14).程序運行過程中,避免使用反射
反射是Java提供給用戶一個很強大的功能偷线,功能強大往往意味著效率不高磨确。不建議在程序運行過程中使用尤其是頻繁使用反射機制,特別是Method的invoke方法声邦,如果確實有必要乏奥,一種建議性的做法是將那些需要通過反射加載的類在項目啟動的時候通過反射實例化出一個對象并放入內存----用戶只關心和對端交互的時候獲取最快的響應速度,并不關心對端的項目啟動花多久時間亥曹。
????????(15).程序中使用連接池和線程池
這兩個池都是用于重用對象的英融,前者可以避免頻繁地打開和關閉連接,后者可以避免頻繁地創(chuàng)建和銷毀線程
?????????(16).使用帶緩沖的輸入輸出流進行IO操作
帶緩沖的輸入輸出流歇式,即BufferedReader驶悟、BufferedWriter、BufferedInputStream材失、BufferedOutputStream痕鳍,這可以極大地提升IO效率
??????????(17).將ArrayList、linkLis運用于合適的場景
??? 順序插入、隨機訪問運用多的是ArrayList,元素刪除笼呆,中間插入較多的是LinkList
?????????? (18).不要讓public方法中有太多形參
public方法即對外提供的方法熊响,如果給這些方法太多形參的話主要有兩點壞處:
違反了面向對象的編程思想,Java講求一切都是對象诗赌,太多的形參汗茄,和面向對象的編程思想并不契合 參數(shù)太多勢必導致方法調用的出錯概率增加 多個形參建議用DTO封裝成對象
?????????????(19).字符串常量和字符串變量使用equals時,常量寫在前面
??????????????(20).把一個基本數(shù)據(jù)類型轉換為字符串
把一個基本數(shù)據(jù)類型轉為一般有三種方式铭若,我有一個Integer型數(shù)據(jù)i洪碳,可以使用i.toString()、String.valueOf(i)叼屠、i+""三種方式瞳腌,效率依次降低
???????????????(21).對于ThreadLocal使用前或使用后一定要remove
當前基本所有的項目都使用了線程池技術,這非常好镜雨,可以動態(tài)配置線程數(shù)嫂侍、可以重用線程。
然而荚坞,如果你在項目中使用到了ThreadLocal挑宠,一定要記得使用前或者使用后remove一下。這是因為上面提到了線程池技術做的是一個線程重用颓影,這意味著代碼運行過程中各淀,一條線程使用完畢,并不會被銷毀而是等待下一次的使用
線程不銷毀意味著上條線程set的ThreadLocal.ThreadLocalMap中的數(shù)據(jù)依然存在瞭空,那么在下一條線程重用這個Thread的時候揪阿,很可能get到的是上條線程set的數(shù)據(jù)而不是自己想要的內容。
這個問題非常隱晦咆畏,一旦出現(xiàn)這個原因導致的錯誤南捂,沒有相關經(jīng)驗或者沒有扎實的基礎非常難發(fā)現(xiàn)這個問題,因此在寫代碼的時候就要注意這一點旧找,這將給你后續(xù)減少很多的工作量溺健。
????????????? (22).循環(huán)體內不要使用+進行字符串拼接,而直接用StirngBuilder的append
意思就是每次虛擬機碰到"+"這個操作符對字符串進行拼接的時候钮蛛,會new出一個StringBuilder鞭缭,然后調用append方法,最后調用toString()方法轉換字符串賦值給對象魏颓,即循環(huán)多少次岭辣,就會new出多少個StringBuilder()來,這對于內存是一種浪費
????????? ??? (23).不捕獲java類庫中定義的繼承RuntimeException的運行異常
異常處理效率低甸饱,RuntimeException的運行時異常類沦童,其中絕大多數(shù)完全可以由程序員來規(guī)避仑濒,比如:
ArithmeticException可以通過判斷除數(shù)是否為空來規(guī)避 NullPointerException可以通過判斷對象是否為空來規(guī)避 IndexOutOfBoundsException可以通過判斷數(shù)組/字符串長度來規(guī)避 ClassCastException可以通過instanceof關鍵字來規(guī)避 ConcurrentModificationException可以使用迭代器來規(guī)避
?????????????(24).把靜態(tài)類、單列類偷遗、工廠類的構造函數(shù)用private修飾
這是因為靜態(tài)類墩瞳、單例類、工廠類這種類本來我們就不需要外部將它們new出來氏豌,將構造函數(shù)置為private之后喉酌,保證了這些類不會產(chǎn)生實例對象。
???????做為一個非正式程序員泵喘,還是得有一種專業(yè)的精神泪电。十分倡導大家像我一樣還是該多學習java代碼的優(yōu)化常識,日常使用時多思考多注意涣旨。為什么要這樣去做歪架?因為我們代碼優(yōu)化最重要的目的股冗,不是為了使得自己擼的某一模塊代碼效率最高霹陡,而是為了防患未知的錯誤。在寫代碼的源頭開始止状,我們就應該注意各種細節(jié)烹棉,權衡使用最優(yōu)的選擇,這樣才能最大程度的避免未知的錯誤怯疤,從長遠來看浆洗,也是提高了效率,真正降低了工作量集峦。