String 、StringBuilder 與 StringBuffer 之間的區別
String
初始化字符串:
String str = "so easy!";
這是字符串初始化的 “簡介版本”,通常字符串的初始化有三種方式:
// 1.最常見,簡短
String str1 = "Hello wrold!";
// 2.構造函數初始化
String str2 = new String("Hello World!");
// 3.字符數組初始化
char[] charArray = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
String str3 = new String(charArray);
然後我們一起看一下 String 類的源碼,再做分析。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = new char[0];
}
/**
* Allocates a new {@code String} so that it represents the sequence of
* characters currently contained in the character array argument. The
* contents of the character array are copied; subsequent modification of
* the character array does not affect the newly created string.
*
* @param value
* The initial value of the string
*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
...
}
private final char value[];
,可以觀察字符串本身是保存在 char 數組中的,換句話說,String 對象其實就是一個字符數組。
這裏需要注意的一件非常重要的事情是,String 類被 final
關鍵字所修飾,public final class String{}
, 這意味着 String 是不可變(immutable)類型。
不可變(immutable) 意味着什麼呢?
String str1 = "Hello World!";
str1.substring(1,4).concat("cbl").toLowerCase().trim().replace('b', 'a');
System.out.println(str1); // Hello World!
上面這段程序的輸出依舊是 Hello World!
。
因爲字符串是 final
的,所以對 str1
所進行的一系列操作並不會改變 str1
本身。這一系列操作返回的是更改後的新字符串,並不會改變 str1
本身。
我們可以看一下 concat()
的源碼:
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
原始字符串永遠不會更改。而是複製一份,並將要連接的文本被添加到複製的字符串之後,最後返回一個新的字符串。
我們用一個變量接收一下返回值,然後輸出,發現返回了一個新的字符串 local
:
String str1 = "Hello World!";
String str2 = str1.substring(3,5).concat("cbl").toLowerCase().trim().replace('b', 'a');
System.out.println(str2); // local
JVM 的原生內存模型爲:
方便我們對 String 、StringBuffer 和 StringBuilder 的理解,我們僅關心下面圖示中的內存區域:
其中 Stack
表示棧區,Heap
表示堆區,String Pool
表示字符串池。
我們再來看看這兩個字符串:
String str1 = "algorithm";
String str2 = "algorithm";
實例化的字符串 str1
和 str2
的值保存在 Java 堆內存中,堆內存用於爲所有 Java 對象動態分配內存。雖然 str1
和 str2
是兩個不同的引用變量,但它們都指向 Java 堆內存中的同一塊內存位置。
雖然看起來有兩個不同的 String 對象,但實際上只有一個。str2
從未被實例化爲對象,而是引用了在堆內存中分配給 str1
的對象。
這是由於 Java 針對字符串進行優化的方式造成的。每次實例化字符串對象時,都會將添加到堆內存的值與之前已添加的值進行比較。如果堆內存中已存在相等的值,則不會初始化該對象,而是將該值賦給引用變量。
像 str1
這樣的常量值保存在一個稱爲常量字符串的池中,簡稱 String Pool,其中包含所有的常量字符串。不過,使用關鍵字 new
創建的字符串 “偷偷” 繞過了 String Pool,而是直接存儲在 Java Heap 當中。
再看一個簡單的例子:
String str1 = "algorithm";
String str2 = "algorithm";
String str3 = new String("algorithm");
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // false
因爲 str1
和 str2
指向內存中的同一對象, str1 == str2
返回爲 true
,str1.equals(str2)
也返回 true
。
因爲 str3
使用關鍵字 new
顯式實例化,所以 String Pool 中雖然已存在字符串文本,依舊會使用構造函數創建一個新對象。
equals()
方法比較的兩個變量的值,而不是它們所指向的內存地址,這就是 str1.equals(str3)
和 str1.equals(str2)
都返回 true
的原因。而 ==
判斷的是兩個變量所指向的內存地址,比較的是真正意義上的指針操作,所以 str1 == str2
返回 true
,而 str1 == str3
返回 false
。
PS:毫不誇張地說,我的一個在阿里實習過的大佬同學,在面試某國企時就跪在了 equals 和 == 的區別上
需要注意 substring()
和 concat()
方法返回一個新的 String 對象,並將其保存在字符串池中。
我們舉的例子都比較簡單,但如果我們考慮一些使用數百個字符串變量和數千個操作(如 substring() 或concat()
)的大型項目,僅使用 String 類可能會導致嚴重的內存泄漏和時間延遲。
這正是爲什麼還要設計 StringBuffer 或 StringBuilder 類的原因。
StringBuffer & StringBuilder
可變性(mutablity)
StringBuffer
和 StringBuilder
對象與 String
對象一樣,三者都是字符序列。但是 StringBuffer
和StringBuilder
是可變的(mutable),String
是不可變的(immutable),這意味着一旦我們爲 StringBuffer
或 StringBuilder
初始化一個值,該值就會作爲 StringBuffer
或 StringBuilder
對象的屬性進行處理。
無論我們修改 StringBuffer
或 StringBuilder
的值多少次,都不會創建新的 String、StringBuffer 或 StringBuilder 對象。StringBuffer
或 StringBuilder
的時間效率更高,資源消耗更少。
StringBuilder vs StringBuffer
這兩個類幾乎完全相同,它們使用同名的方法,返回相同的結果。但它們之間有兩個主要區別:
-
線程安全
StringBuffer
類是同步的(Synchronized),一次只能有一個線程調用StringBuffer
實例的方法。StringBuilder
類是不同步的,多個線程可以調用StringBuilder
類中的方法,而不會被阻塞。因此,StringBuffer
是線程安全的,而StringBuffer
不是。如果我們開發的是多線程的應用程序,那麼使用StringBuilder
可能會有風險。 -
效率
StringBuffer
實際上比StringBuilder
慢 2~3 倍。原因是StringBuffer
線程安全(一次只允許一個線程在一個對象上執行),會導致代碼執行速度慢得多。
方法
StringBuilder
和 StringBuffer
擁有相同的方法(除 StringBuffer
的方法都是 synchronized
的)。兩者常用的幾個方法:append()
、insert
、replace()
、delete()
、reverse()
。
String vs StringBuilder vs StringBuffer
-
String StringBuilder StringBuffer
- 可變性 不可變 可變 可變
- 線程安全 是 否 是
- 時間效率 低 較低 高
- 內存效率 低 高 高
注意:從上表中看到的,String
類在時間和內存上的效率都較低,但這並不意味着我們不應該再使用它。
事實上,String
可以非常方便地使用,因爲它可以快速編寫,我在實際開發經常使用 public static final String
來定義字符串常量。
String concatString = "concatString";
StringBuffer appendBuffer = new StringBuffer("appendBuffer");
StringBuilder appendBuilder = new StringBuilder("appendBuilder");
long timerStarted;
timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
concatString += " another string";
}
System.out.println("50000次String concat 操作的時間:" + (System.currentTimeMillis() - timerStarted) + "ms");
timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
appendBuffer.append(" another string");
}
System.out.println("50000次 StringBuffer 的 append 操作時間:" + (System.currentTimeMillis() - timerStarted) + "ms");
timerStarted = System.currentTimeMillis();
for (int i = 0; i < 50000; i++) {
appendBuilder.append(" another string");
}
System.out.println("50000次 StringBuilder 的 append 操作時間:" + (System.currentTimeMillis() - timerStarted) + "ms");
輸出:
50000次String concat 操作的時間:18108ms
50000次 StringBuffer 的 append 操作時間:7ms
50000次 StringBuilder 的 append 操作時間:3ms
根據您的 Java 虛擬機的不同,此輸出可能會有所不同。僅從這個測試結果可以看出,StringBuilder
在字符串操作方面是最快的。速度次快的是 StringBuffer
,它比 StringBuilder
慢 2~3 倍。String
的 concat
操作最慢。
連接同樣多的字符串,StringBuilder
比 String
快約 6000 倍,換句話說,StringBuilder
在 1 秒內可以連接的字符串,String
需要 1.6 小時。
總結
**可變性:**String 是不可變類型,如果試圖更改 String 對象的值,則會在 String Pool 中創建另一個對象,而 StringBuffer 和 StringBuilder 是可變的,其值可以更改。
**線程安全:**StringBuffer 和 StringBuilder 之間的區別就在於 StringBuffer 是線程安全的。因此,當應用程序只需要在單線程中運行時,最好使用 StringBuilder ,因爲 StringBuilder 比 StringBuffer 更高效。
使用建議:
-
如果程序中的字符串不會更改,比如 Java 中常量字符串的定義(
public static final String
),建議使用 String 類,因爲 String 對象本身就是final
的。 -
如果程序中字符串需要改變,且僅是單線程訪問,那麼使用 StringBuilder 就足夠了。
-
如果程序中字符串需要改變,且爲多個線程訪問,建議使用 StringBuffer,因爲 StringBuffer 是同步的,可以保證線程安全。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/2jLN2X2V0y2KtRryhGSHGg