文章來源于公眾號:程序新視界 作者:丑胖俠二師兄
關(guān)于字符串的面試題除了內(nèi)存分布、equals
比較,最常見的就是與StringBuffer
和StringBuilder
之間的區(qū)別了。
如果你回答:String
類是不可變的,StringBuffer
和StringBuilder
是可變類,StringBuffer
是線程安全的,StringBuilder
則不是線程安全的。
就上面的總結(jié)而言,好像知道的有點少。本篇文章就帶領(lǐng)大家全面的了解一下它們?nèi)齻€的區(qū)別與底層實現(xiàn)。
String字符串的拼接
關(guān)于String
字符串前面多篇文章已經(jīng)詳細描述過,它的不可變性也是因為每當通過“+”操作時,都會在內(nèi)存中生成新的字符串而導致的。
String a = "hello ";
String b = "world!";
String ab = a + b;
針對上述代碼,內(nèi)存分布圖如下:
其中 a 和 b 初始化時位于字符串常量池,ab 拼接后的對象位于堆中。可以很直觀的看出,經(jīng)過拼接新生成了String
對象。如果拼接多次,那么會生成多個中間對象。
上面的結(jié)論在Java8
之前是成立的,在Java8
時 JDK 對“+”號拼接進行了優(yōu)化,上面所寫的拼接方式會被優(yōu)化為基于StringBuilder
的append
方法進行處理。
stack=2, locals=4, args_size=1
0: ldc #2 // String hello
2: astore_1
3: ldc #3 // String world!
5: astore_2
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: return
上面是通過 javap -verbose
命令反編譯字節(jié)碼的結(jié)果,很顯然可以看到StringBuilder
的創(chuàng)建和`append方法的調(diào)用。
此時,如果再籠統(tǒng)的回答:通過加號拼接字符串會創(chuàng)建多個String
對象,因此性能比StringBuilder
差,就是錯誤的了。因為本質(zhì)上加號拼接的效果最終經(jīng)過編譯器處理之后和StringBuilder
是一致的。
如果你在代碼中使用如下寫法:
StringBuilder sb = new StringBuilder("hello ");
sb.append("world!");
System.out.println(sb.toString());
編譯器的插件甚至建議你使用String
來代替。
StringBuffer與StringBuilder的對比
StringBuffer
和StringBuilder
實現(xiàn)的核心代碼基本一致,很多代碼都是公用的。這兩個類均繼承自抽象類AbstractStringBuilder
。
我們來從構(gòu)造方法到append
方法來逐一看一下它們的區(qū)別。先看StringBuilder
的構(gòu)造方法:
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
其中super
方法便是調(diào)用的AbstractStringBuilder
的構(gòu)造方法。對應(yīng)StringBuffer
的構(gòu)造方法中實現(xiàn)也是如此:
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
從構(gòu)造方法來說,StringBuffer
和StringBuilder
是一樣的。下面再看看append
方法,StringBuilder
實現(xiàn)如下:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
StringBuffer
對應(yīng)的方法如下:
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
很顯然,在StringBuffer
的append
方法實現(xiàn)上除了內(nèi)部將toStringCache
變量賦值為null
,唯一的不同就是在方法上使用synchronized
進行了同步處理。
toStringCache
是用來緩存最后一次調(diào)用toString
方法時生成的字符串,當StringBuffer
內(nèi)容變動時,該值也會變動。
通過上面的append
方法的對比,我們可以很輕易的發(fā)現(xiàn)StringBuffer
是線程安全的,StringBuilder
是非線程安全的。當然,使用synchronized
進行同步處理,性能便會降低很多。
StringBuffer與StringBuilder的底層實現(xiàn)
StringBuffer
與StringBuilder
都調(diào)用了父類的構(gòu)造方法:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
通過該構(gòu)造方法我們可以看到它們用來處理字符串信息的關(guān)鍵屬性為value
。在初始化時先初始化一個長度為傳入字符串長度+16的char[]
數(shù)組,也就是value
值,用來存儲實際的字符串。
在調(diào)用父類構(gòu)造方法之后便是調(diào)用各自的append方法(見前面的代碼),而其中的核心處理又的調(diào)用父類的append方法:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
上述代碼中其中str.getChars
方法用來對傳入的str
字符串進行拼接,在原有的value
數(shù)組后面進行填充。而count
用來記錄當前value
數(shù)字中已經(jīng)使用的長度。
那么,當沒有使用synchronized
進行同步操作時,線程不安全發(fā)生在哪里?上面代碼中count+=len
并不是原子操作。比如當前count
為 5,兩個線程同時執(zhí)行到 ++ 操作,拿到的值都為 5,執(zhí)行完加操作之后賦值給count
,兩個線程賦值都為 6,而不是 7。此時便出現(xiàn)了線程不安全的問題。
為什么String要設(shè)計成不可變
在 Java 中將String
設(shè)計成不可變的是綜合考慮到各種因素的結(jié)果,有如下原因:
1、字符串常量池的需要,如果字符串可變,改變一個對象會影響到另外一個獨立的對象。不變這也是字符串常量池存在的前提條件。
2、Java 中String
對象的哈希碼被頻繁地使用,比如在HashMap等容器中。字符串不變保證了hash碼的唯一性,可以方向緩存并使用。
3、安全性,確保String
在當做參數(shù)傳遞時保持不變,避免安全隱患。比如在數(shù)據(jù)庫用戶名、密碼、訪問路徑等傳輸過程中的保持不變,防止改變字符串指向?qū)ο蟮闹当桓淖儭?/p>
4、由于字符串變量不可變,在多線程中可以被共享使用。
小結(jié)
單純的死記硬背面試題我們都會,但要在記憶面試題的過程中了解更多底層實現(xiàn)原理,不僅僅有助于理解“為什么”,同時還能學到更多相關(guān)的知識和原理。
在本文中簡化了StringBuilder
和StringBuffer
內(nèi)部數(shù)據(jù)的 copy 、數(shù)組擴容等步驟的講解,感興趣的朋友可以繼續(xù)對照源碼進行深入研究。
以上就是W3Cschool編程獅
關(guān)于Java面試題:談?wù)凷tring、StringBuffer、StringBuilder的區(qū)別?的相關(guān)介紹了,希望對大家有所幫助。