String 、StringBuilder 與 StringBuffer 之間的區(qū)別
點擊上方“程序員大白”,選擇“星標”公眾號
重磅干貨,第一時間送達
String
初始化字符串:
String str = "so easy!";
這是字符串初始化的 “簡介版本”,通常字符串的初始化有三種方式:
// 1.最常見,簡短
String str1 = "Hello wrold!";
// 2.構造函數(shù)初始化
String str2 = new String("Hello World!");
// 3.字符數(shù)組初始化
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 數(shù)組中的,換句話說,String 對象其實就是一個字符數(shù)組。
這里需要注意的一件非常重要的事情是,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);
}
原始字符串永遠不會更改。而是復制一份,并將要連接的文本被添加到復制的字符串之后,最后返回一個新的字符串。
我們用一個變量接收一下返回值,然后輸出,發(fā)現(xiàn)返回了一個新的字符串 local :
String str1 = "Hello World!";
String str2 = str1.substring(3,5).concat("cbl").toLowerCase().trim().replace('b', 'a');
System.out.println(str2); // local
JVM 的原生內(nèi)存模型為:

方便我們對 String 、StringBuffer 和 StringBuilder 的理解,我們僅關心下面圖示中的內(nèi)存區(qū)域:

其中 Stack 表示棧區(qū),Heap 表示堆區(qū),String Pool 表示字符串池。
我們再來看看這兩個字符串:
String str1 = "algorithm";
String str2 = "algorithm";
實例化的字符串 str1 和 str2 的值保存在Java 堆內(nèi)存中,堆內(nèi)存用于為所有 Java 對象動態(tài)分配內(nèi)存。雖然 str1 和 str2 是兩個不同的引用變量,但它們都指向 Java 堆內(nèi)存中的同一塊內(nèi)存位置。
雖然看起來有兩個不同的 String 對象,但實際上只有一個。str2 從未被實例化為對象,而是引用了在堆內(nèi)存中分配給 str1 的對象。

這是由于 Java 針對字符串進行優(yōu)化的方式造成的。每次實例化字符串對象時,都會將添加到堆內(nèi)存的值與之前已添加的值進行比較。如果堆內(nèi)存中已存在相等的值,則不會初始化該對象,而是將該值賦給引用變量。
像 str1 這樣的常量值保存在一個稱為常量字符串的池中,簡稱 String Pool,其中包含所有的常量字符串。不過,使用關鍵字 new 創(chuàng)建的字符串 “偷偷” 繞過了 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 指向內(nèi)存中的同一對象, str1 == str2 返回為 true ,str1.equals(str2) 也返回 true 。
因為 str3 使用關鍵字 new 顯式實例化,所以 String Pool 中雖然已存在字符串文本,依舊會使用構造函數(shù)創(chuàng)建一個新對象。
equals() 方法比較的兩個變量的值,而不是它們所指向的內(nèi)存地址,這就是 str1.equals(str3) 和 str1.equals(str2) 都返回 true 的原因。而 == 判斷的是兩個變量所指向的內(nèi)存地址,比較的是真正意義上的指針操作,所以 str1 == str2 返回 true ,而 str1 == str3 返回 false 。
PS:毫不夸張地說,我的一個在阿里實習過的大佬同學,在面試某國企時就跪在了 equals 和 == 的區(qū)別上
需要注意 substring() 和 concat() 方法返回一個新的 String 對象,并將其保存在字符串池中。
我們舉的例子都比較簡單,但如果我們考慮一些使用數(shù)百個字符串變量和數(shù)千個操作(如 substring() 或concat())的大型項目,僅使用 String 類可能會導致嚴重的內(nèi)存泄漏和時間延遲。
這正是為什么還要設計 StringBuffer 或 StringBuilder 類的原因。
StringBuffer & StringBuilder
可變性(mutablity)
StringBuffer 和 StringBuilder 對象與 String 對象一樣,三者都是字符序列。但是 StringBuffer 和StringBuilder 是可變的(mutable),String 是不可變的(immutable),這意味著一旦我們?yōu)?StringBuffer 或 StringBuilder 初始化一個值,該值就會作為 StringBuffer 或 StringBuilder 對象的屬性進行處理。
無論我們修改 StringBuffer 或 StringBuilder 的值多少次,都不會創(chuàng)建新的 String、StringBuffer 或StringBuilder 對象。StringBuffer 或 StringBuilder 的時間效率更高,資源消耗更少。
StringBuilder vs StringBuffer
這兩個類幾乎完全相同,它們使用同名的方法,返回相同的結果。但它們之間有兩個主要區(qū)別:
線程安全
StringBuffer類是同步的(Synchronized),一次只能有一個線程調(diào)用StringBuffer實例的方法。StringBuilder類是不同步的,多個線程可以調(diào)用StringBuilder類中的方法,而不會被阻塞。因此,StringBuffer是線程安全的,而StringBuffer不是。如果我們開發(fā)的是多線程的應用程序,那么使用StringBuilder可能會有風險。效率
StringBuffer實際上比StringBuilder慢 2~3 倍。原因是StringBuffer線程安全(一次只允許一個線程在一個對象上執(zhí)行),會導致代碼執(zhí)行速度慢得多。
方法
StringBuilder 和 StringBuffer 擁有相同的方法(除 StringBuffer 的方法都是 synchronized 的)。兩者常用的幾個方法:append() 、insert 、replace() 、delete() 、reverse() 。
String vs StringBuilder vs StringBuffer
| String | StringBuilder | StringBuffer | |
|---|---|---|---|
| 可變性 | 不可變 | 可變 | 可變 |
| 線程安全 | 是 | 否 | 是 |
| 時間效率 | 低 | 較低 | 高 |
| 內(nèi)存效率 | 低 | 高 | 高 |
注意:從上表中看到的,String 類在時間和內(nèi)存上的效率都較低,但這并不意味著我們不應該再使用它。
事實上,String 可以非常方便地使用,因為它可以快速編寫,我在實際開發(fā)經(jīng)常使用 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
根據(jù)您的Java虛擬機的不同,此輸出可能會有所不同。僅從這個測試結果可以看出,StringBuilder 在字符串操作方面是最快的。速度次快的是 StringBuffer ,它比 StringBuilder 慢 2~3 倍。String 的 concat 操作最慢。
連接同樣多的字符串,StringBuilder 比 String 快約 6000 倍,換句話說,StringBuilder 在 1 秒內(nèi)可以連接的字符串,String 需要 1.6 小時。
總結
可變性:String 是不可變類型,如果試圖更改 String 對象的值,則會在 String Pool 中創(chuàng)建另一個對象,而 StringBuffer 和StringBuilder 是可變的,其值可以更改。
線程安全:StringBuffer 和 StringBuilder 之間的區(qū)別就在于 StringBuffer 是線程安全的。因此,當應用程序只需要在單線程中運行時,最好使用 StringBuilder ,因為 StringBuilder 比 StringBuffer 更高效。
使用建議:
如果程序中的字符串不會更改,比如 Java 中常量字符串的定義( public static final String),建議使用 String 類,因為 String 對象本身就是final的。如果程序中字符串需要改變,且僅是單線程訪問,那么使用 StringBuilder 就足夠了。 如果程序中字符串需要改變,且為多個線程訪問,建議使用 StringBuffer,因為 StringBuffer 是同步的,可以保證線程安全。
推薦閱讀
國產(chǎn)小眾瀏覽器因屏蔽視頻廣告,被索賠100萬(后續(xù))
年輕人“不講武德”:因看黃片上癮,把網(wǎng)站和786名女主播起訴了
關于程序員大白
程序員大白是一群哈工大,東北大學,西湖大學和上海交通大學的碩士博士運營維護的號,大家樂于分享高質量文章,喜歡總結知識,歡迎關注[程序員大白],大家一起學習進步!


