如何在Java中避免建立不必要的物件(備戰2022春招或暑期實習,每天進步一點點,打卡100天,Day1)

語言: CN / TW / HK

本文已參與 「掘力星計劃」 ,贏取創作大禮包,挑戰創作激勵金。

  • 備戰2022春招或暑期實習,祝大家每天進步億點點!Day1
  • 本篇總結的是 如何在Java中避免建立不必要的物件,後續會每日更新~
  • 關於《Redis入門到精通》、《併發程式設計》等知識點可以參考我的往期部落格:xxx
  • 相信自己,越活越堅強,活著就該逢山開路,遇水架橋!生活,你給我壓力,我還你奇蹟!

簡介

在Java開發中,程式設計師要儘可能的避免建立相同的功能的物件,因為這樣既消耗記憶體,又影響程式執行速度。在這種情況下可以考慮重複利用物件

接下來舉例幾種物件重複利用的場景,看看我們是不是有中招了,如果有趕緊趁著還沒被發現悄悄改掉,被發現了會被diss啦!

1、String和Boolean

如下兩種寫法看似沒有什麼區別,但是如果深入jvm底層瞭解,我們可以利用jvm執行時常量池的特性,避免建立具有相同功能的String物件(尤其是在迴圈內部建立)可以帶來比較可觀的效能優化以及節約記憶體。

錯誤寫法

// 每次都會建立一個新的String物件,且不會加入常量池 String name2 = new String("李子捌");

正確寫法

// 正確寫法 String name1 = "李子捌";

除此之外,剛寫Java程式碼的程式設計師們,也要正確的選擇String、StringBuilder、StringBuffer類的使用。String為不可變物件,通常用於定義不變字串;StringBuilder、StringBuffer用於可變字串操作場景,如字串拼接;其中StringBuffer是執行緒安全的,它通過Synchronized關鍵字來實現執行緒同步。

``` // StringBuffer中的append()方法 public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }

// StringBuilder中的append()方法 public StringBuilder append(String str) { super.append(str); return this; } ```

Boolean是常用的型別,在開發中也應該使用Boolean.valueof()而不是new Boolean(),從Boolean的原始碼可以看出,Boolean類定義了兩個final static的屬性,而Boolean.valueof()直接返回的是定義的這兩個屬性,而new Boolean()卻會建立新的物件。

``` public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false); ```

2、自動拆箱和裝箱

Java提供了基本資料型別的自動拆箱和裝箱功能,那是不是意味著我們可以在程式碼中隨意的使用這兩個型別呢?其實理論上在程式碼層面是沒得問題,不過在具體的效能方面還是有優化的空間啦!!!

我們來測試下效能

long start = System.currentTimeMillis(); Integer sum = 0; for (int i = 0; i < 100000; i++) { sum += i; } System.out.println(System.currentTimeMillis() - start);

使用Integer耗時3毫秒

long start = System.currentTimeMillis(); // 修改Integer 為 int int sum = 0; for (int i = 0; i < 100000; i++) { sum += i; } System.out.println(System.currentTimeMillis() - start);

使用int耗時0毫秒

因此關於自動拆箱裝箱的使用,我們其實也可以做適當的考慮,畢竟有時候程式碼效能就是一點點擠出來的嘛!!!

3、正則表示式

正則表示式我們經常用於字串是否合法的校驗,我們先來看一段簡單的程式碼(大家有沒有一眼看出問題呢?我想你肯定看出來了!!!):

``` public static void main(String[] args) {

String email = "[email protected]";
String regex = "^([a-z0-9A-Z]+[-|\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\.)+[a-zA-Z]{2,}$";

long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
    email.matches(regex);
}

System.out.println(System.currentTimeMillis() - start);

} ```

執行這段程式碼的時間,一共耗時71毫秒,看似好像挺快的!

但是我們做個非常簡單的優化,優化後的程式碼如下所示:

``` public static void main(String[] args) {

String email = "[email protected]";
String regex = "^([a-z0-9A-Z]+[-|\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\.)+[a-zA-Z]{2,}$";
Pattern pattern = Pattern.compile(regex);

long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
    //email.matches(regex);
    pattern.matcher(email);
}

System.out.println(System.currentTimeMillis() - start);

} ```

再次執行程式碼,一共耗時1毫秒,快了70倍呀!!!

這是因為String.matches()方法在迴圈中建立時,每次都需要執行Pattern.compile(regex),而建立Patter例項的成本很高,因為需要將正則表示式編譯成一個有限狀態機( finite state machine)。這種我們經常會因為Java api提供了比較方便的方法呼叫而忽略了效能考究,往往不容易被發現。這個時候就需要優秀的你,去“咬文嚼字”啦!

「歡迎在評論區討論,掘金官方將在掘力星計劃活動結束後,在評論區抽送100份掘金周邊,抽獎詳情見活動文章」