博客 / 詳情

返回

【後端面經-Java】String與StringBuffer與StringBuilder的比較

1. String

  1. 不可變
    查看String源碼如下:

    public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
        /** The value is used for character storage. */
        private final char value[];
    
        /** The offset is the first index of the storage that is used. */
        private final int offset;
    
        /** The count is the number of characters in the String. */
        private final int count;
    
        /** Cache the hash code for the string */
        private int hash; // Default to 0
    
        /** use serialVersionUID from JDK 1.0.2 for interoperability */
        private static final long serialVersionUID = -6849794470754667710L;
        
        ......
    }

    由源碼可知,String中存儲數據的數組被關鍵字final修飾,因此是不可變的

  2. 運算和操作

    1. 創建對象
      創建對象有兩種方式:

      //方式一
        String str = "abc";
      //方式二
        String str = new String("abc");

      兩種方式都會在棧中創建一個字符串變量str,但它們的內存分配方式是不同的。
      我們可以通過如下代碼直觀看出兩種方式的不同

        String str1 = "abc";
        String str2 = "abc";
        String str3 = new String("abc");
        String str4 = new String("abc");
        System.out.println(str1 == str2); //true
        System.out.println(str3 == str4); //false

      為了理解這部分內容,最好先了解一下Java中的內存分配機制,可參考此篇博客:【後端面經-Java】JVM內存分區詳解
      總之,簡單來説,內存主要分為棧、堆、方法區等部分,棧中存放局部變量,堆中存放對象實例和數組,方法區中存放類信息和常量等,常量池一開始均在方法區中,後來運行時常量池轉移到堆中,下文均按照這種內存分配模型來討論。
      下圖展示了兩種創建方式下的內存情況:

      • 方式一

        • 在棧中創建一個變量之後,需要指向具體的值,首先會在常量池中查找abc,如果找到,則指向這個字符串,如果沒有找到,在運行時常量池中創建這一字符串,然後指向它。
        • 因此,str1str2指向的是同一個字符串,即同一個內存單元,所以str1 == str2true
      • 方式二

        • 在棧中創建一個變量之後,在堆中構造一個新的字符串對象,然後指向它。
        • 因此,str3str4指向的是兩個不同的內存單元,所以str3 == str4false
    2. "+"運算

      • 每次"+"運算雖然看似很簡便,實際上需要創建一個新的String對象來接收結果,而作為運算數的String對象依然存在於堆中,成為垃圾佔用堆空間,需要Java垃圾回收機制進行處理。(關於Java垃圾回收機制,可參考此篇博文:【後端面經-Java】JVM垃圾回收機制)
      • 這種操作是非常低效的,且造成了大量的內存佔用,因此在實際開發中,應儘量避免使用"+"運算符來進行字符串拼接,而應該使用StringBufferStringBuilder來進行字符串拼接。
    3. substring() && replace() && concat()

      • 這些操作的一個特點就是:創建新的String對象承接結果,而原來的String對象依然存在於堆中。
  3. 適用場景

    1. 適用於字符串不需修改的場景。

      2. StringBuffer

  4. 可變

    1. StringBuffer源代碼中數組是可變長度的。
  5. 線程安全

    1. 在類定義過程中,適用synchronized關鍵字,保證線程安全。
    2. 線程安全與否是StringBufferStringBuilder的重要區別之一。
  6. 運算和操作

    1. append():在字符串末尾添加新字符串;
    2. insert():在指定位置插入新字符串;
    3. toString():將StringBuffer轉換為String
  7. 適用場景

    • 多線程,字符串需要頻繁修改

      3. StringBuilder

  8. 可變

    1. StringBuffer一樣,StringBuilder源代碼中數組是可變長度的。
  9. 線程不安全

    1. 並沒有使用synchronized關鍵字,因此線程不安全。
    2. 因為線程不安全,不需要考慮線程安全的處理,所以StringBuilder的性能比StringBuffer略高。
  10. 適用場景:

    1. 單線程,字符串需要頻繁修改

4. 性能提升

  1. 為了提升性能,避免在字符串需要修改的場景下使用String類;
  2. 在初始定義時預先估計字符串的長度,對StringBuilderStringBuffer進行初始化,避免頻繁擴容,提升性能。
 StringBuilder sb = new StringBuilder(100);
 StringBuffer sb = new StringBuffer(100);
``

## 5. 總結和比較
下圖是對三者進行的比較:
![](https://cdn.jsdelivr.net/gh/cyl173/Imagebed/String相關類對比.png)


## 面試模擬
> Q:簡單介紹一下String和StringBuilder的區別
> A:首先,String定義的字符串是不可變的,使用拼接函數或者操作符將會創建一個新的String對象,性能不高;而StringBuilder定義的字符串可變,且線程不安全使得其具有較好的性能,在字符串需要頻繁修改的場景下,使用StringBuilder會更好。


## 參考資料
1. [Java String、StringBuffer 和 StringBuilder 的區別](https://www.runoob.com/w3cnote/java-different-of-string-stringbuffer-stringbuilder.html)
2. [Java中String和StringBuilder的區別](https://www.techiedelight.com/zh/difference-between-string-stringbuilder-java/)
user avatar dm2box 頭像 docker_app 頭像 cunyu1943 頭像 yadong_zhang 頭像 deltaf 頭像 saoming_zhang 頭像 jianhuan 頭像 wodingshangniliao 頭像 u_16213560 頭像 jellyfishmix 頭像 yimin333 頭像 lingfeng_5c71377321873 頭像
18 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.