為什麼阿裡巴巴Java開發手冊中不建議在迴圈體中使用+進行字元串拼接?

来源:https://www.cnblogs.com/wupeixuan/archive/2019/10/24/11729920.html
-Advertisement-
Play Games

之前在閱讀《阿裡巴巴Java開發手冊》時,發現有一條是關於迴圈體中字元串拼接的建議,具體內容如下: 那麼我們首先來用例子來看看在迴圈體中用 + 或者用 StringBuilder 進行字元串拼接的效率如何吧(JDK版本為 jdk1.8.0_201)。 可以看出,第 8 行到第 38 行構成了一個迴圈 ...


之前在閱讀《阿裡巴巴Java開發手冊》時,發現有一條是關於迴圈體中字元串拼接的建議,具體內容如下:

阿裡巴巴Java開發手冊

那麼我們首先來用例子來看看在迴圈體中用 + 或者用 StringBuilder 進行字元串拼接的效率如何吧(JDK版本為 jdk1.8.0_201)。

package com.wupx.demo;

/**
 * @author wupx
 * @date 2019/10/23
 */
public class StringConcatDemo {
    public static void main(String[] args) {
        long s1 = System.currentTimeMillis();
        new StringConcatDemo().addMethod();
        System.out.println("使用 + 拼接:" + (System.currentTimeMillis() - s1));

        s1 = System.currentTimeMillis();
        new StringConcatDemo().stringBuilderMethod();
        System.out.println("使用 StringBuilder 拼接:" + (System.currentTimeMillis() - s1));
    }

    public String addMethod() {
        String result = "";
        for (int i = 0; i < 100000; i++) {
            result += (i + "武培軒");
        }
        return result;
    }

    public String stringBuilderMethod() {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < 100000; i++) {
            result.append(i).append("武培軒");
        }
        return result.toString();
    }
}

執行結果如下:

使用 + 拼接:29282
使用 StringBuilder 拼接:4

為什麼這兩種方法的時間會差這麼多呢?接下來讓我們一起進一步研究。

為什麼 StringBuilder 比 + 快這麼多?

從位元組碼層面來看下,為什麼迴圈體中字元串拼接 StringBuilder 比 + 快這麼多?

使用 javac StringConcatDemo.java 命令編譯源文件,使用 javap -c StringConcatDemo 命令查看位元組碼文件的內容。

其中 addMethod() 方法的位元組碼如下:

  public java.lang.String addMethod();
    Code:
       0: ldc           #16                 // String
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: ldc           #17                 // int 100000
       8: if_icmpge     41
      11: new           #7                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      18: aload_1
      19: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: iload_2
      23: invokevirtual #18                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      26: ldc           #19                 // String wupx
      28: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      31: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      34: astore_1
      35: iinc          2, 1
      38: goto          5
      41: aload_1
      42: areturn

可以看出,第 8 行到第 38 行構成了一個迴圈體:在第 8 行的時候做條件判斷,如果不滿足迴圈條件,則跳轉到 41 行。編譯器做了一定程度的優化,在 11 行 new 了一個 StringBuilder 對象,然後再 19 行、23 行、28 行進行了三次 append() 方法的調用,不過每次迴圈都會重新 new 一個 StringBuilder 對象。

再來看 stringBuilderMethod() 方法的位元組碼:

  public java.lang.String stringBuilderMethod();
    Code:
       0: new           #7                  // class java/lang/StringBuilder
       3: dup
       4: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
       7: astore_1
       8: iconst_0
       9: istore_2
      10: iload_2
      11: ldc           #17                 // int 100000
      13: if_icmpge     33
      16: aload_1
      17: iload_2
      18: invokevirtual #18                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      21: ldc           #19                 // String wupx
      23: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      26: pop
      27: iinc          2, 1
      30: goto          10
      33: aload_1
      34: invokevirtual #12                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: areturn

13 行到 30 行構成了迴圈體,可以看出,在第4行(迴圈體外)就構建好了 StringBuilder 對象,然後再迴圈體內只進行 append() 方法的調用。

由此可以看出,在 for 迴圈中,使用 + 進行字元串拼接,每次都是 new 了一個 StringBuilder,然後再把 String 轉成 StringBuilder,再進行 append,而頻繁的新建對象不僅要耗費很多時間,還會造成記憶體資源的浪費。這就從位元組碼層面解釋了為什麼不建議在迴圈體內使用 + 去進行字元串的拼接。

接下來再來讓我們看下使用 + 或者 StringBuilder 拼接字元串的原理吧。

使用 + 拼接字元串

在 Java 開發中,最簡單常用的字元串拼接方法就是直接使用 + 來完成:

String boy = "wupx";
String girl = "huyx";
String love = boy + girl;

反編譯後的內容如下:(使用的反編譯工具為 jad)

String boy = "wupx";
String girl = "huyx";
String love = (new StringBuilder()).append(boy).append(girl).toString();

通過查看反編譯以後的代碼,可以發現,在字元串常量在拼接過程中,是將 String 轉成了 StringBuilder 後,使用其 append() 方法進行處理的。

那麼也就是說,Java中的 + 對字元串的拼接,其實現原理是使用 StringBuilder 的 append() 來實現的,使用 + 拼接字元串,其實只是 Java 提供的一個語法糖。

使用 StringBuilder 拼接字元串

StringBuilder 的 append 方法就是第二個常用的字元串拼接姿勢了。

和 String 類類似,StringBuilder 類也封裝了一個字元數組,定義如下:

char[] value;

與 String 不同的是,它並不是 final 的,所以是可以修改的。另外,與 String 不同,字元數組中不一定所有位置都已經被使用,它有一個實例變數,表示數組中已經使用的字元個數,定義如下:

int count;

其 append() 方法源碼如下:

public StringBuilder append(String str) {
   super.append(str);
   return this;
}

該類繼承了 AbstractStringBuilder 類,看下其 append() 方法:

public AbstractStringBuilder append(String str) {
    if (str == null)
        return appendNull();
    int len = str.length();
    ensureCapacityInternal(count + len);
    str.getChars(0, len, value, count);
    count += len;
    return this;
}

首先判斷拼接的字元串 str 是不是 null,如果是,調用 appendNull() 方法進行處理,appendNull() 方法的源碼如下:

private AbstractStringBuilder appendNull() {
    int c = count;
    ensureCapacityInternal(c + 4);
    final char[] value = this.value;
    value[c++] = 'n';
    value[c++] = 'u';
    value[c++] = 'l';
    value[c++] = 'l';
    count = c;
    return this;
}

如果字元串 str 不為 null,則判斷拼接後的字元數組長度是否超過當前數組長度,如果超過,則調用 Arrays.copyOf() 方法進行擴容並複製,ensureCapacityInternal() 方法的源碼如下:

private void ensureCapacityInternal(int minimumCapacity) {
    if (minimumCapacity - value.length > 0) {
        value = Arrays.copyOf(value,
                newCapacity(minimumCapacity));
    }
}

最後,將拼接的字元串 str 複製到目標數組 value 中。

str.getChars(0, len, value, count);

總結

本文針對《阿裡巴巴Java開發手冊》中的迴圈體中拼接字元串建議出發,從位元組碼層面,來解釋為什麼 StringBuilder 比 + 快,還分別介紹了字元串拼接中 + 和 StringBuilder 的原理,因此在迴圈體拼接字元串時,應該使用 StringBuilder 的 append() 去完成拼接。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 話不多說,先展示效果圖。由於錄製工具,稍顯卡頓,實際是流暢的。可以看到實現了無縫輪播,滑鼠懸停,點擊左右上下按鈕切換Banner的功能,如圖1所示。 圖1 原生無縫banner效果展示 以我這個輪播圖為例,總共3張圖的Banner輪播圖,實際上是由5張圖組成,如圖2所示。一張圖片長544px,所以s ...
  • 小程式生成圖片分享朋友圈 小程式開發者都希望自己的小程式得以廣泛傳播,因為不少小程式都設計了很多轉發激勵行為,但分享小程式到朋友圈(或其他外部平臺)一直是一個難題。一個常見但方案就是生成分享海報、分享圖片。但生成分享圖片在技術上卻也是一個難題。 技術選型 目前常用技術方案基本分為三種: 1. 使用 ...
  • border 可應用於幾乎所有有形的html元素,而outline 是針對鏈接、表單控制項和ImageMap等元素設計。 outline的效果將隨元素的 focus 而自動出現,相應的隨 blur 而自動消失。這些都是瀏覽器的預設行為,無需JavaScript配合CSS來控制。outline 不會像b ...
  • <a href="javascript:void(0);"></a> <!--按照格式要求,此處的0不能省略!! 雖然省略看上去也沒什麼影響。但是當發生點擊事件的時候, 就會報錯: Uncaught SyntaxError: Unexpected token ) --> <!--或者像下麵這樣: -... ...
  • 函數內部聲明變數的時候,一定要使用var命令。如果不用的話,你實際上聲明瞭一個全局變數! 子函數可以一層一層讀取到父元素的變數,反之不行。但假如我們需要得到函數內的局部變數,正常操作是:在函數的內部再定義一個函數,該函數稱之為閉包,通過閉包,向上去讀取父函數的局部變數。閉包的作用:讀取局部變數;讓局 ...
  • 效果: ...
  • 這兩天在研究閉包,網上一通找,有牛人寫的帖子,有普通人寫的帖子,但是大多沒戳中本小白所糾結的點,而且大多插入了立即執行函數,其實根本不需要的,反而讓人產生了誤解。這裡我用我的方式講解一下閉包。 1.目的:保證局部變數常駐在記憶體中,且只能通過固定的方式訪問,不可以被所有人訪問,算起來也算是個只能被指定 ...
  • 使用匿名自執行函數的作用: (function(window,document,undefined){})(window,document); 1.首先匿名函數 (function(){}) (); 避免函數體內外變數的衝突(js執行表達式順序為圓括弧里到圓括弧外); 2.後面的圓括弧中(windo ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...