[TOC](【後端面經-Java】String與StringBuffer與StringBuilder的比較) ## 1. String 1. 不可變 查看`String`源碼如下: ```java public final class String implements java.io.Serial ...
目錄
1. String
- 不可變
查看String
源碼如下:
由源碼可知,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; ...... }
final
修飾,因此是不可變的 - 運算和操作
- 創建對象
創建對象有兩種方式:
兩種方式都會在棧中創建一個字元串變數//方式一 String str = "abc"; //方式二 String str = new String("abc");
str
,但它們的記憶體分配方式是不同的。
我們可以通過如下代碼直觀看出兩種方式的不同
為了理解這部分內容,最好先瞭解一下Java中的記憶體分配機制,可參考此篇博客:【後端面經-Java】JVM記憶體分區詳解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
總之,簡單來說,記憶體主要分為棧、堆、方法區等部分,棧中存放局部變數,堆中存放對象實例和數組,方法區中存放類信息和常量等,常量池
一開始均在方法區中,後來運行時常量池
轉移到堆中,下文均按照這種記憶體分配模型來討論。
下圖展示了兩種創建方式下的記憶體情況:
- 方式一:
- 在棧中創建一個變數之後,需要指向具體的值,首先會在常量池中查找
abc
,如果找到,則指向這個字元串,如果沒有找到,在運行時常量池
中創建這一字元串,然後指向它。 - 因此,
str1
和str2
指向的是同一個字元串,即同一個記憶體單元,所以str1 == str2
為true
- 方式二
- 在棧中創建一個變數之後,在堆中構造一個新的字元串對象,然後指向它。
- 因此,
str3
和str4
指向的是兩個不同的記憶體單元,所以str3 == str4
為false
- "+"運算
- 每次"+"運算雖然看似很簡便,實際上需要創建一個新的String對象來接收結果,而作為運算數的String對象依然存在於堆中,成為垃圾占用堆空間,需要Java垃圾回收機制進行處理。(關於Java垃圾回收機制,可參考此篇博文:【後端面經-Java】JVM垃圾回收機制)
- 這種操作是非常低效的,且造成了大量的記憶體占用,因此在實際開發中,應儘量避免使用"+"運算符來進行字元串拼接,而應該使用
StringBuffer
或StringBuilder
來進行字元串拼接。
- substring() && replace() && concat()
- 這些操作的一個特點就是:創建新的String對象承接結果,而原來的String對象依然存在於堆中。
- 創建對象
- 適用場景
- 適用於字元串不需修改的場景。
2. StringBuffer
- 可變
- StringBuffer源代碼中數組是可變長度的。
- 線程安全
- 在類定義過程中,適用
synchronized
關鍵字,保證線程安全。 - 線程安全與否是
StringBuffer
和StringBuilder
的重要區別之一。
- 在類定義過程中,適用
- 運算和操作
append()
:在字元串末尾添加新字元串;insert()
:在指定位置插入新字元串;toString()
:將StringBuffer
轉換為String
;
- 適用場景
- 多線程,字元串需要頻繁修改
3. StringBuilder
- 可變
- 和
StringBuffer
一樣,StringBuilder
源代碼中數組是可變長度的。
- 和
- 線程不安全
- 並沒有使用
synchronized
關鍵字,因此線程不安全。 - 因為線程不安全,不需要考慮線程安全的處理,所以
StringBuilder
的性能比StringBuffer
略高。
- 並沒有使用
- 適用場景:
- 單線程,字元串需要頻繁修改
4. 性能提升
- 為了提升性能,避免在字元串需要修改的場景下使用
String
類; - 在初始定義時預先估計字元串的長度,對
StringBuilder
和StringBuffer
進行初始化,避免頻繁擴容,提升性能。StringBuilder sb = new StringBuilder(100); StringBuffer sb = new StringBuffer(100); ``
5. 總結和比較
下圖是對三者進行的比較:
面試模擬
Q:簡單介紹一下String和StringBuilder的區別
A:首先,String定義的字元串是不可變的,使用拼接函數或者操作符將會創建一個新的String對象,性能不高;而StringBuilder定義的字元串可變,且線程不安全使得其具有較好的性能,在字元串需要頻繁修改的場景下,使用StringBuilder會更好。
參考資料
- Java String、StringBuffer 和 StringBuilder 的區別
- Java中String和StringBuilder的區別
- 探秘Java中的String、StringBuilder以及StringBuffer