1. 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; ...... }由源碼可知,String中存儲數據的數組被關鍵字
final修飾,因此是不可變的 -
運算和操作
-
創建對象
創建對象有兩種方式://方式一 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,如果找到,則指向這個字符串,如果沒有找到,在運行時常量池中創建這一字符串,然後指向它。 - 因此,
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會更好。
## 參考資料
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/)