Java中功能優化的35種辦法匯總。本站提示廣大學習愛好者:(Java中功能優化的35種辦法匯總)文章只能為提供參考,不一定能成為您想要的結果。以下是Java中功能優化的35種辦法匯總正文
前言
對順序員們來說,代碼優化是一個很重要的課題。能夠有些人覺得沒用,一些粗大的中央有什麼好修正的,改與不改關於代碼的運轉效率有什麼影響呢?這個問題我是這麼思索的,好像大海外面的鯨魚一樣,它吃一條小蝦米有用嗎?沒用,但是,吃的小蝦米一多之後,鯨魚就被喂飽了。代碼優化也是一樣,假如項目著眼於盡快無BUG上線,那麼此時可以抓大放小,代碼的細節可以不精打細磨;但是假如有足夠的時間開發、維護代碼,這時分就必需思索每個可以優化的細節了,一個一個粗大的優化點累積起來,關於代碼的運轉效率相對是有提升的。
代碼優化的目的是:
1、減小代碼的體積
2、進步代碼運轉的效率
代碼優化細節
1、盡量指定類、辦法的final修飾符
帶有final修飾符的類是不可派生的。在Java中心API中,有許多使用final的例子,例如java.lang.String
,整個類都是final的。為類指定final修飾符可以讓類不可以被承繼,為辦法指定final修飾符可以讓辦法不可以被重寫。假如指定了一個類為final,則該類一切的辦法都是final的。Java編譯器會尋覓時機內聯一切的final辦法,內聯關於提升Java運轉效率作用嚴重,詳細參見Java運轉期優化。此舉可以使功能均勻進步50%。
2、盡量重用對象
特別是String對象的運用,呈現字符串銜接時應該運用StringBuilder/StringBuffer替代。由於Java虛擬機不只要花時間生成對象,當前能夠還需求花時間對這些對象停止渣滓回收和處置,因而,生成過多的對象將會給順序的功能帶來很大的影響。
3、盡能夠運用部分變量
調用辦法時傳遞的參數以及在調用中創立的暫時變量都保管在棧中速度較快,其他變量,如靜態變量、實例變量等,都在堆中創立,速度較慢。另外,棧中創立的變量,隨著辦法的運轉完畢,這些內容就沒了,不需求額定的渣滓回收。
4、及時封閉流
Java編程進程中,停止數據庫銜接、I/O流操作時務必小心,在運用終了後,及時封閉以釋放資源。由於對這些大對象的操作會形成零碎大的開支,稍有不慎,將會招致嚴重的結果。
5、盡量增加對變量的反復計算
明白一個概念,對辦法的調用,即便辦法中只要一句語句,也是有耗費的,包括創立棧幀、調用辦法時維護現場、調用辦法終了時恢復現場等。所以例如上面的操作:
for (int i = 0; i < list.size(); i++) {...}
建議交換為:
for (int i = 0, int length = list.size(); i < length; i++) {...}
這樣,在list.size()
很大的時分,就增加了很多的耗費
6、盡量采用懶加載的戰略,即在需求的時分才創立
例如:
String str = "aaa";if (i == 1) { list.add(str); }
建議交換為:
if (i == 1) { String str = "aaa"; list.add(str); }
7、慎用異常
異常對功能不利。拋出異常首先要創立一個新的對象,Throwable接口的結構函數調用名為fillInStackTrace()
的本地同步辦法,fillInStackTrace()
辦法反省堆棧,搜集調用跟蹤信息。只需有異常被拋出,Java虛擬機就必需調整調用堆棧,由於在處置進程中創立了一個新的對象。異常只能用於錯誤處置,不應該用來控制順序流程。
8、不要在循環中運用try…catch…,應該把其放在最外層
除非不得已。假如毫在理由地這麼寫了,只需你的指導資深一點、有強迫症一點,八成就要罵你為什麼寫出這種渣滓代碼來了
9、假如能估量到待添加的內容長度,為底層以數組方式完成的集合、工具類指定初始長度
比方ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder為例:
(1)StringBuilder()
// 默許分配16個字符的空間
(2)StringBuilder(int size)
// 默許分配size個字符的空間
(3)StringBuilder(String str)
// 默許分配16個字符+str.length()
個字符空間
可以經過類(這裡指的不只僅是下面的StringBuilder)的來設定它的初始化容量,這樣可以分明地提升功能。比方StringBuilder吧,length表示以後的StringBuilder能堅持的字符數量。由於當StringBuilder到達最大容量的時分,它會將本身容量添加到以後的2倍再加2,無論何時只需StringBuilder到達它的最大容量,它就不得不創立一個新的字符數組然後將舊的字符數組內容拷貝到新字符數組中—-這是非常消耗功能的一個操作。試想,假如能預估到字符數組中大約要寄存5000個字符而不指定長度,最接近5000的2次冪是4096,每次擴容加的2不論,那麼:
(1)在4096 的根底上,再請求8194個大小的字符數組,加起來相當於一次請求了12290個大小的字符數組,假如一開端能指定5000個大小的字符數組,就節省了一倍以上的空間
(2)把原來的4096個字符拷貝到新的的字符數組中去
這樣,既糜費內存空間又降低代碼運轉效率。所以,給底層以數組完成的集合、工具類設置一個合理的初始化容量是錯不了的,這會帶來立竿見影的效果。但是,留意,像HashMap這種是以數組+鏈表完成的集合,別把初始大小和你估量的大小設置得一樣,由於一個table上只銜接一個對象的能夠性簡直為0。初始大小建議設置為2的N次冪,假如能估量到有2000個元素,設置成new HashMap(128)
、new HashMap(256)
都可以。
10、當復制少量數據時,運用System.arraycopy()命令
11、乘法和除法運用移位操作
例如:
for (val = 0; val < 100000; val += 5) { a = val * 8; b = val / 2; }
用移位操作可以極大地進步功能,由於在計算機底層,對位的操作是最方便、最快的,因而建議修正為:
for (val = 0; val < 100000; val += 5) { a = val << 3; b = val >> 1; }
移位操作雖然快,但是能夠會使代碼不太好了解,因而最好加上相應的正文。
12、循環內不要不時創立對象援用
例如:
for (int i = 1; i <= count; i++) {Object obj = new Object(); }
這種做法會招致內存中有count份Object對象援用存在,count很大的話,就消耗內存了,建議為改為:
Object obj = null;for (int i = 0; i <= count; i++) { obj = new Object(); }
這樣的話,內存中只要一份Object對象援用,每次new Object()
的時分,Object對象援用指向不同的Object罷了,但是內存中只要一份,這樣就大小節省了內存空間了。
13、基於效率和類型反省的思索,應該盡能夠運用array,無法確定數組大小時才運用ArrayList
14、盡量運用HashMap、ArrayList、StringBuilder,除非線程平安需求,否則不引薦運用Hashtable、Vector、StringBuffer,後三者由於運用同步機制而招致了功能開支
15、不要將數組聲明為public static final
由於這毫有意義,這樣只是定義了援用為static final,數組的內容還是可以隨意改動的,將數組聲明為public更是一個平安破綻,這意味著這個數組可以被內部類所改動
16、盡量在適宜的場所運用單例
運用單例可以加重加載的擔負、延長加載的時間、進步加載的效率,但並不是一切中央都適用於單例,復雜來說,單例次要適用於以下三個方面:
(1)控制資源的運用,經過線程同步來控制資源的並發訪問
(2)控制實例的發生,以到達浪費資源的目的
(3)控制數據的共享,在不樹立直接關聯的條件下,讓多個不相關的進程或線程之間完成通訊
17、盡量防止隨意運用靜態變量
要知道,當某個對象被定義為static的變量所援用,那麼gc通常是不會回收這個對象所占有的堆內存的,如:
public class A { private static B b = new B(); }
此時靜態變量b的生命周期與A類相反,假如A類不被卸載,那麼援用B指向的B對象會常駐內存,直到順序終止
18、及時肅清不再需求的會話
為了肅清不再活動的會話,許多使用服務器都有默許的會話超時時間,普通為30分鐘。當使用服務器需求保管更多的會話時,假如內存缺乏,那麼操作零碎會把局部數據轉移到磁盤,使用服務器也能夠依據MRU(最近最頻繁運用)算法把局部不活潑的會話轉儲到磁盤,甚至能夠拋出內存缺乏的異常。假如會話要被轉儲到磁盤,那麼必需要先被序列化,在大規模集群中,對對象停止序列化的代價是很昂貴的。因而,當會話不再需求時,該當及時調用HttpSession的invalidate()
辦法肅清會話。
19、完成RandomAccess接口的集合比方ArrayList,該當運用最普通的for循環而不是foreach循環來遍歷
這是JDK引薦給用戶的。JDK API關於RandomAccess接口的解釋是:完成RandomAccess接口用來標明其支持疾速隨機訪問,此接口的次要目的是允許普通的算法更改其行為,從而將其使用到隨機或延續訪問列表時能提供良好的功能。實踐經歷標明,完成RandomAccess接口的類實例,假設是隨機訪問的,運用普通for循環效率將高於運用foreach循環;反過去,假如是順序訪問的,則運用Iterator會效率更高。可以運用相似如下的代碼作判別:
if (list instanceof RandomAccess) { for (int i = 0; i < list.size(); i++){} }else{ Iterator iterator = list.iterable(); while (iterator.hasNext()){iterator.next()} }
foreach循環的底層完成原理就是迭代器Iterator,參見Java語法糖1:可變長度參數以及foreach循環原理。所當前半句”反過去,假如是順序訪問的,則運用Iterator會效率更高”的意思就是順序訪問的那些類實例,運用foreach循環去遍歷。
20、運用同步代碼塊替代同步辦法
這點在多線程模塊中的synchronized鎖辦法塊一文中曾經講得很清楚了,除非能確定一整個辦法都是需求停止同步的,否則盡量運用同步代碼塊,防止對那些不需求停止同步的代碼也停止了同步,影響了代碼執行效率。
21、將常量聲明為static final,並以大寫命名
這樣在編譯時期就可以把這些內容放入常量池中,防止運轉時期計算生成常量的值。另外,將常量的名字以大寫命名也可以方便區分出常量與變量
22、不要創立一些不運用的對象,不要導入一些不運用的類
這毫有意義,假如代碼中呈現”The value of the local variable i is not used”、”The import java.util is never used”,那麼請刪除這些無用的內容
23、順序運轉進程中防止運用反射
關於,請參見反射。反射是Java提供應用戶一個很弱小的功用,功用弱小往往意味著效率不高。不建議在順序運轉進程中運用尤其是頻繁運用反射機制,特別是Method的invoke辦法,假如的確有必要,一種建議性的做法是將那些需求經過反射加載的類在項目啟動的時分經過反射實例化出一個對象並放入內存—-用戶只關懷和對端交互的時分獲取最快的呼應速度,並不關懷對端的項目啟動花多久時間。
24、運用數據庫銜接池和線程池
這兩個池都是用於重用對象的,前者可以防止頻繁地翻開和封閉銜接,後者可以防止頻繁地創立和銷毀線程
25、運用帶緩沖的輸出輸入流停止IO操作
帶緩沖的輸出輸入流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,這可以極大地提升IO效率
26、順序拔出和隨機訪問比擬多的場景運用ArrayList,元素刪除和兩頭拔出比擬多的場景運用LinkedList
這個,了解ArrayList和LinkedList的原理就知道了
27、不要讓public辦法中有太多的形參
public辦法即對外提供的辦法,假如給這些辦法太多形參的話次要有兩點害處:
1、違背了面向對象的編程思想,Java講求一切都是對象,太多的形參,和面向對象的編程思想並不契合
2、參數太多勢必招致辦法調用的出錯概率添加
至於這個”太多”指的是多少個,3、4個吧。比方我們用JDBC寫一個insertStudentInfo辦法,有10個學生信息字段要插如Student表中,可以把這10個參數封裝在一個實體類中,作為insert辦法的形參
28、字符串變量和字符串常量equals的時分將字符串常量寫在後面
這是一個比擬罕見的小技巧了,假如有以下代碼:
String str = "123";if (str.equals("123")) {...}
建議修正為:
String str = "123";if ("123".equals(str)) {...}
這麼做次要是可以防止空指針異常
29、請知道,在java中if (i == 1)和if (1 == i)是沒有區別的,但從閱讀習氣上講,建議運用前者
平常有人問,”if (i == 1)
”和”if (1== i)
”有沒有區別,這就要從C/C++講起。
在C/C++中,”if (i == 1)
”判別條件成立,是以0與非0為基准的,0表示false,非0表示true,假如有這麼一段代碼:
int i = 2;if (i == 1) {...}else{...}
C/C++判別”i==1
″不成立,所以以0表示,即false。但是假如:
int i = 2;if (i = 1) { ... }else{ ... }
萬一順序員一個不小心,把”if (i == 1)
”寫成”if (i = 1)
”,這樣就有問題了。在if之內將i賦值為1,if判別外面的內容非0,前往的就是true了,但是明明i為2,比擬的值是1,應該前往的false。這種狀況在C/C++的開發中是很能夠發作的並且會招致一些難以了解的錯誤發生,所以,為了防止開發者在if語句中不正確的賦值操作,建議將if語句寫為:
int i = 2;if (1 == i) { ... }else{ ... }
這樣,即便開發者不小心寫成了”1 = i
”,C/C++編譯器也可以第一時間反省出來,由於我們可以對一個變量賦值i為1,但是不能對一個常量賦值1為i。
但是,在Java中,C/C++這種”if (i = 1)
”的語法是不能夠呈現的,由於一旦寫了這種語法,Java就會編譯報錯”Type mismatch: cannot convert from int to boolean
”。但是,雖然Java的”if (i == 1)
”和”if (1 == i)
”在語義上沒有任何區別,但是從閱讀習氣上講,建議運用前者會更好些。
30、不要對數組運用toString()辦法
看一下對數組運用toString()
打印出來的是什麼:
public static void main(String[] args) { int[] is = new int[]{1, 2, 3}; System.out.println(is.toString()); }
後果是:
[I@18a992f
本意是想打印出數組內容,卻有能夠由於數組援用is為空而招致空指針異常。不過雖然對數組toString()
沒有意義,但是對集合toString()
是可以打印出集合外面的內容的,由於集合的父類AbstractCollections重寫了Object的toString()
辦法。
31、不要對超出范圍的根本數據類型做向下強迫轉型
這絕不會失掉想要的後果:
public static void main(String[] args) { long l = 12345678901234L;int i = (int)l; System.out.println(i); }
我們能夠希冀失掉其中的某幾位,但是後果卻是:
1942892530
解釋一下。Java中long是8個字節64位的,所以12345678901234在計算機中的表示應該是:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010
一個int型數據是4個字節32位的,從低位取出下面這串二進制數據的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010
這串二進制表示為十進制1942892530,所以就是我們下面的控制台上輸入的內容。
從這個例子上還能特地失掉兩個結論:
1、整型默許的數據類型是int,long l = 12345678901234L,這個數字曾經超出了int的范圍了,所以最後有一個L,表示這是一個long型數。特地,浮點型的默許類型是double,所以定義float的時分要寫成””float f = 3.5f”
2、接上去再寫一句”int ii = l + i;
”會報錯,由於long + int是一個long,不能賦值給int
32、公用的集合類中不運用的數據一定要及時remove掉
假如一個集合類是公用的(也就是說不是辦法外面的屬性),那麼這個集合外面的元素是不會自動釋放的,由於一直有援用指向它們。所以,假如公用集合外面的某些數據不運用而不去remove掉它們,那麼將會形成這個公用集合不時增大,使得零碎有內存洩露的隱患。
33、把一個根本數據類型轉為字符串,根本數據類型.toString()是最快的方式、String.valueOf(數據)次之、數據+””最慢
把一個根本數據類型轉為普通有三種方式,我有一個Integer型數據i,可以運用i.toString()
、String.valueOf(i)
、i+””三種方式,三種方式的效率如何,看一個測試:
public static void main(String[] args) { int loopTime = 50000; Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = String.valueOf(i); } System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = i.toString(); } System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = i + ""; } System.out.println("i + \"\":" + (System.currentTimeMillis() - startTime) + "ms"); }
運轉後果為:
String.valueOf():11ms Integer.toString():5ms i + "":25ms
所以當前遇到把一個根本數據類型轉為String的時分,優先思索運用toString()
辦法。
至於為什麼,很復雜:
1、String.valueOf()
辦法底層調用了Integer.toString()
辦法,但是會在調用前做空判別
2、Integer.toString()
辦法就不說了,直接調用了
3、i + “”底層運用了StringBuilder完成,先用append辦法拼接,再用toString()
辦法獲取字符串
三者比照上去,分明是2最快、1次之、3最慢
34、運用最無效率的方式去遍歷Map
遍歷Map的方式有很多,通常場景下我們需求的是遍歷Map中的Key和Value,那麼引薦運用的、效率最高的方式是:
public static void main(String[] args) { HashMap hm = new HashMap(); hm.put("111", "222");Set> entrySet = hm.entrySet(); Iterator> iter = entrySet.iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); System.out.println(entry.getKey() + "\t" + entry.getValue()); } }
假如你只是想遍歷一下這個Map的key值,那用”Set keySet = hm.keySet();
”會比擬適宜一些
35、對資源的close()建議分開操作
意思是,比方我有這麼一段代碼:
try{ XXX.close(); YYY.close(); }catch (Exception e) {...}
建議修正為:
try{ XXX.close(); }catch (Exception e) { ... }try{ YYY.close(); }catch (Exception e) { ... }
雖然有些費事,卻能防止資源洩露。我們想,假如沒有修正過的代碼,萬一XXX.close()
拋異常了,那麼就進入了cath塊中了,YYY.close()
不會執行,YYY這塊資源就不會回收了,不斷占用著,這樣的代碼一多,是能夠惹起資源句柄洩露的。而改為上面的寫法之後,就保證了無論如何XXX和YYY都會被close掉。
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家學習或許運用Java能帶來一定的協助,假如有疑問大家可以留言交流。