天天看點

Java日常開發代碼優化緣由優化

Java日常開發代碼優化

不積跬步無以至千裡,不積小流無以成江河。如果說考慮的工期等因素,代碼能按期正常無bug運作上線就算項目完成,如果想讓項目運作的更流程,使用者體驗更好,必要的代碼細節還是需要仔細考慮的,每次優化一點點,日積月累的優化也是會有質的改變的。

1.盡量重用對象

特别是 String 對象的使用,出現字元串連接配接時應該使用 StringBuilder/StringBuffer 代替。由于 Java 虛拟機不僅要花時間生成對象,以後可能還需要花時間對這些對象進行垃圾回收和處理,是以,生成過多的對象将會給程式的性能帶來很大的影響。

2.盡可能使用局部變量

調用方法時傳遞的參數以及在調用中建立的臨時變量都儲存在棧中速度較快,其他變量,如靜态變量、執行個體變量等,都在堆中建立,速度較慢。另外,棧中建立的變量,随着方法的運作結束,這些内容就沒了,不需要額外的垃圾回收。

3.及時關閉流

Java 程式設計過程中,進行資料庫連接配接、I/O 流操作時務必小心,在使用完畢後,及時關閉以釋放資源。因為對這些大對象的操作會造成系統大的開銷,稍有不慎,将會導緻嚴重的後果。

4.盡量減少對變量的重複計算

明确一個概念,對方法的調用,即使方法中隻有一句語句,也是有消耗的,包括建立棧幀、調用方法時保護現場、調用方法完畢時恢複現場等。

Java日常開發代碼優化緣由優化

這樣的話每一次周遊都需要進行一次dataList.size()操作,可以更換為:

Java日常開發代碼優化緣由優化

5.盡量采用懶加載的政策,即在需要的時候才建立

Java日常開發代碼優化緣由優化

可以替換成:

Java日常開發代碼優化緣由優化

這樣操作的話就是等真的需要用到這個參數了再加載擷取,如果用不到的話就擷取,其實是沒有意義的。

6.慎用異常

異常對性能不利。抛出異常首先要建立一個新的對象,

Java日常開發代碼優化緣由優化

Throwable 接口的構造函數調用名為 fillInStackTrace() 的本地同步方法,

Java日常開發代碼優化緣由優化

fillInStackTrace() 方法檢查堆棧,收集調用跟蹤資訊。

Java日常開發代碼優化緣由優化

隻要有異常被抛出,Java 虛拟機就必須調整調用堆棧,因為在處理過程中建立了一個新的對象。

Java日常開發代碼優化緣由優化

異常隻能用于錯誤處理,不應該用來控制程式流程。

7.不要在循環中使用try…catch…,應該把其放在最外層

8.如果能估計到待添加的内容長度,為底層以數組方式實作的集合、工具類指定初始長度

比如 ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以 StringBuilder 為例:

StringBuilder() // 預設配置設定16個字元的空間,(父類AbstractStringBuilder)

Java日常開發代碼優化緣由優化

StringBuilder(int size) // 預設配置設定size個字元的空間

Java日常開發代碼優化緣由優化

StringBuilder(String str) // 預設配置設定16個字元+str.length()個字元空間

Java日常開發代碼優化緣由優化

可以通過類(這裡指的不僅僅是上面的 StringBuilder)的來設定它的初始化容量,這樣可以明顯地提升性能。比如 StringBuilder吧,length 表示目前的 StringBuilder 能保持的字元數量。因為當 StringBuilder 達到最大容量的時候,它會将自身容量增加到目前的2倍再加2,

Java日常開發代碼優化緣由優化
Java日常開發代碼優化緣由優化

無論何時隻要 StringBuilder 達到它的最大容量,它就不得不建立一個新的字元數組然後将舊的字元數組内容拷貝到新字元數組中—-這是十分耗費性能的一個操作。試想,如果能預估到字元數組中大概要存放5000個字元而不指定長度,最接近5000的2次幂是4096,每次擴容加的2不管,那麼:

在4096 的基礎上,再申請8194個大小的字元數組,加起來相當于一次申請了12290個大小的字元數組,如果一開始能指定5000個大小的字元數組,就節省了一倍以上的空間;

把原來的4096個字元拷貝到新的的字元數組中去。

這樣,既浪費記憶體空間又降低代碼運作效率。是以,給底層以數組實作的集合、工具類設定一個合理的初始化容量是錯不了的,這會帶來立竿見影的效果。但是,注意,像 HashMap 這種是以數組+連結清單實作的集合,别把初始大小和你估計的大小設定得一樣,因為一個 table 上隻連接配接一個對象的可能性幾乎為0。初始大小建議設定為2的N次幂,如果能估計到有2000個元素,設定成 new HashMap(128)、new HashMap(256) 都可以。

9.當複制大量資料時,使用System.arraycopy()指令(目前遇到的業務上未用到大量複制資料操作)

10.乘法和除法使用移位操作

Java日常開發代碼優化緣由優化

執行結果:

Java日常開發代碼優化緣由優化

11.循環内不要不斷建立對象引用

Java日常開發代碼優化緣由優化

這樣做的話會導緻記憶體中有size份對象的引用,當size很大的時候,就比較耗費記憶體。

可以替換為:

Java日常開發代碼優化緣由優化

這樣改之後的話記憶體中隻有一份對象引用,每次new的時候,隻是對象引用指向不同的對象,但是記憶體中隻有一份,這樣也就節省了記憶體空間。

12.盡量使用HashMap、ArrayList、StringBuilder,除非線程安全需要,否則不推薦使用Hashtable、Vector、StringBuffer,後三者由于使用同步機制而導緻了性能開銷。

13.盡量避免随意使用靜态變量

要知道,當某個對象被定義為 static 的變量所引用,那麼 GC 通常是不會回收這個對象所占有的堆記憶體的。

14.對資源的close()建議分開操作

Java日常開發代碼優化緣由優化

可以修改為:

Java日常開發代碼優化緣由優化

這樣做的益處是如果in.close();執行時發生異常那麼也不影響out.close();的執行,第一種的話如果in.close發生異常,則out.close将永遠不會執行,就會一直占用資源。

15.使用最有效率的方式去周遊Map

周遊Map的方式很多,通常場景下我們需要擷取map的key和value值,推薦的方式如下:

Java日常開發代碼優化緣由優化

如果隻是想周遊一下map的key值,那麼可以用map.keySet();擷取key值集合後周遊取值。

16.把一個基本資料類型轉為字元串,obj.toString()是最快的方式、String.valueOf(obj)次之、obj+“”最慢,下面我們來看一下為什麼這麼說,首先看String.valueOf(obj):

Java日常開發代碼優化緣由優化

可以看到String.valueOf(obj)内部還是obj.toString(),同時增加了null判斷;

obj+“”,底層使用了 StringBuilder 實作,先用 append() 方法拼接,再用 toString() 方法擷取字元串。

17.公用的集合類中不使用的資料一定要及時remove掉

如果一個集合類是公用的(也就是說不是方法裡面的屬性),那麼這個集合裡面的元素是不會自動釋放的,因為始終有引用指向它們。是以,如果公用集合裡面的某些資料不使用而不去 remove 掉它們,那麼将會造成這個公用集合不斷增大,使得系統有記憶體洩露的隐患。

18.不要對超出範圍的基本資料類型做向下強制轉型

public static void main(String[] args) {
        long a = 123456789123456L;
        System.out.println("long="+a);
        int b = (int) a;
        System.out.println("int="+b);
    }      
long=123456789123456
int=-2045800064      

強轉之後得到的結果并不是我們想要的結果,這是因為a值已經超出int的範圍。

19.不要對數組使用toString()方法

對數組用toString方法沒有意義

public static void main(String[] args) {
        String[] a = new String[]{"nihao","java","php"};
        System.out.println(a.toString());
    }      
[Ljava.lang.String;@67f89fa3      

想要擷取具體某個值時可以通過System.out.println(a[0]);角标擷取。

20.在java中if(i == 1) 和 if(1 == i)是沒有差別的,但從閱讀習慣上講,建議使用前者。

21.字元串變量和字元串常量equals的時候将字元串常量寫在前面

public static void main(String[] args) {
        String a = "nihao";
        if ("nihao".equals(a)) {
            System.out.println("相同");
        }
    }      

22.不要讓public方法中有太多的形參,太多形參的話一方面看起來比較low,另一方面就是别人調用的時候容易搞亂參數對應位置導緻出錯,針對于多參數的時候可以自定義對象傳參或者封裝map傳參均可以。

23.順序插入和随機通路比較多的場景使用ArrayList,元素删除和中間插入比較多的場景使用LinkedList這個。

24.使用帶緩沖的輸入輸出流進行IO操作,帶緩沖的輸入輸出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,這可以極大地提升 IO 效率。

25.使用資料庫連接配接池和線程池

這兩個池都是用于重用對象的,前者可以避免頻繁地打開和關閉連接配接,後者可以避免頻繁地建立和銷毀線程。

26.不要建立一些不使用的對象,不要導入一些不使用的類

這毫無意義,如果代碼中出現“The value of the local variable i is not used”、“The import java.util is never used”,那麼請删除這些無用的内容。

27.将常量聲明為static final,并以大寫命名

這樣在編譯期間就可以把這些内容放入常量池中,避免運作期間計算生成常量的值。另外,将常量的名字以大寫命名也可以友善區分出常量與變量。

28.使用同步代碼塊替代同步方法

這點在多線程子產品中的 synchronized 鎖方法塊一文中已經講得很清楚了,除非能确定一整個方法都是需要進行同步的,否則盡量使用同步代碼塊,避免對那些不需要進行同步的代碼也進行了同步,影響了代碼執行效率。

29.實作RandomAccess接口的集合比如ArrayList,應當使用最普通的for循環而不是foreach循環來周遊

官方解釋:Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.

The best algorithms for manipulating random access lists (such as ArrayList) can produce quadratic behavior when applied to sequential access lists (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.

It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if,for typical instances of the class, this loop:

for (int i=0, n=list.size(); i < n; i++)

list.get(i);

runs faster than this loop:

for (Iterator i=list.iterator(); i.hasNext(); )

i.next();

百度翻譯:

清單實作使用的标記接口,用于訓示它們支援快速(通常為固定時間)随機通路。此接口的主要目的是允許通用算法在應用于随機或順序通路清單時改變其行為,以提供良好的性能。

操作随機通路清單的最佳算法(如ArrayList)在應用于順序通路清單(如LinkedList)時會産生二次行為。鼓勵通用清單算法在應用算法之前檢查給定清單是否為該接口的執行個體,如果将該算法應用于順序通路清單,則該算法将提供較差的性能,并在必要時改變其行為,以確定可接受的性能。

人們認識到,随機存取和順序存取之間的差別通常是模糊的。例如,一些List實作提供了漸近線性的通路時間,如果它們的通路時間很大,但實際上是恒定的。這種清單實作通常應實作此接口。根據經驗,如果對于類的典型執行個體,此循環。。。

實際經驗表明,實作 RandomAccess 接口的類執行個體,假如是随機通路的,使用普通 for 循環效率将高于使用 foreach 循環;反過來,如果是順序通路的,則使用 Iterator 會效率更高。