java程式碼編寫優化(持續更新...)

左耳聽風發表於2018-05-05

1:使用final修飾方法與屬性
被final宣告的方法與屬性會被jvm快取與優化,在編譯期就關聯進來,用於:引數,方法,類,常量(結合static)
final意味不可改變(不可改變的資料那麼jvm是不是可以進行快取)
static意味全域性唯一(全域性唯一那麼是不是記憶體空間只此一份)

2:StringBuilder/StringBuffer代替String
3:及時關閉流:資料庫連線,io,file,redis等
4:儘量減少對變數的重複計算,如重複計算則新建一個變數

1for (int i = 0; i < list.size(); i++){...}
2//替換為
3for (int i = 0, length = list.size(); i < length; i++){...}

5:使用懶載入策略,有使用到再建立
6:慎用異常
異常對效能不利。丟擲異常首先要建立一個新的物件,Throwable介面的建構函式呼叫名為fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆疊,收集呼叫跟蹤資訊。只要有異常被丟擲,Java虛擬機器就必須調整呼叫堆疊,因為在處理過程中建立了一個新的物件。異常只能用於錯誤處理,不應該用來控制程式流程。
7:乘除法使用位移操作

1for (val = 0; val < 100000; val += 5){
2  a = val * 8;
3  b = val / 2;
4}

用移位操作可以極大地提高效能,因為在計算機底層,對位的操作是最方便、最快的,因此建議修改為:

1for (val = 0; val < 100000; val += 5){
2  a = val << 3;
3  b = val >> 1;
4}

移位操作雖然快,但是可能會使程式碼不太好理解,因此最好加上相應的註釋。

8:迴圈內不要不斷建立物件引用
例如:

1for (int i = 1; i <= count; i++){
2    Object obj = new Object();    
3}

這種做法會導致記憶體中有count份Object物件引用存在,count很大的話,就耗費記憶體了,建議為改為:

1Object obj = null;
2for (int i = 0; i <= count; i++){
3    obj = new Object();
4}

這樣的話,記憶體中只有一份Object物件引用,每次new Object()的時候,Object物件引用指向不同的Object罷了,但是記憶體中只有一份,這樣就大大節省了記憶體空間了。
9:基於效率和型別檢查的考慮,應該儘可能使用array,無法確定陣列大小時才使用ArrayList

10:儘量使用HashMap、ArrayList、StringBuilder,除非執行緒安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,後三者由於使用同步機制而導致了效能開銷
正常我們寫程式碼的時候不直接用後面三個自帶鎖的,而是自己在程式碼塊上加鎖,然後再使用前面三個

11:使用資料庫連線池和執行緒池
這兩個池都是用於重用物件的,前者可以避免頻繁地開啟和關閉連線,後者可以避免頻繁地建立和銷燬執行緒

12:對比小技巧
字串對比寫法:"123".equals(str);
null判斷寫法:null==str;
變數怕判斷:1==num;
這麼做主要是可以避免空指標異常與忘記寫雙等號

13:不要對超出範圍的基本資料型別做向下強制轉型

1long l = 12345678901234L;
2int i = (int)l;
3System.out.println(i);

14:把一個基本資料型別轉為字串,基本資料型別.toString()是最快的方式、String.valueOf(資料)次之、資料+""最慢
String.valueOf()方法底層呼叫了Integer.toString()方法,但是會在呼叫前做空判斷
Integer.toString()方法就不說了,直接呼叫了
i + ""底層使用了StringBuilder實現,先用append方法拼接,再用toString()方法獲取字串

15:使用最有效率的方式去遍歷Map
遍歷Map的方式有很多,通常場景下我們需要的是遍歷Map中的Key和Value,那麼推薦使用的、效率最高的方式是:

 1public static void main(String[] args){
 2    HashMap<String, String> hm = new HashMap<String, String>();
 3    hm.put("111", "222");
 4    Set<Map.Entry<String, String>> entrySet = hm.entrySet();
 5    Iterator<Map.Entry<String, String>> iter = entrySet.iterator();
 6    while (iter.hasNext()) {
 7        Map.Entry<String, String> entry = iter.next();
 8        System.out.println(entry.getKey() + "\t" + entry.getValue());
 9    }
10}

如果你只是想遍歷一下這個Map的key值,那用"Set keySet = hm.keySet();"會比較合適一些

16:對於ThreadLocal使用前或者使用後一定要先remove
如果你在專案中使用到了ThreadLocal,一定要記得使用前或者使用後remove一下。這是因為上面提到了執行緒池技術做的是一個執行緒重用,這意味著程式碼執行過程中,一條執行緒使用完畢,並不會被銷燬而是等待下一次的使用。
執行緒不銷燬意味著上條執行緒set的ThreadLocal.ThreadLocalMap中的資料依然存在,那麼在下一條執行緒重用這個Thread的時候,很可能get到的是上條執行緒set的資料而不是自己想要的內容。

17:避免Random例項被多執行緒使用,雖然共享該例項是執行緒安全的,但會因競爭同一seed 導致的效能下降,JDK7之後,可以使用ThreadLocalRandom來獲取隨機數

18:儘量使用棧變數。
呼叫方法時傳遞的引數以及在呼叫中建立的臨時變數都儲存在棧(Stack)中,速度較快。
靜態變數,例項變數等,都在堆(Heap)中建立,速度較慢。

1//儲存在堆上
2Integer i = 817598;
3// 儲存在棧上
4int i = 817598;
5在使用陣列時情況可能會變得更加糟糕:
6//在堆上生成了三個物件
7Integer[] i = { 1337, 424242 };
8// 僅在堆上生成了一個物件
9int[] i = { 1337, 424242 };

相關文章