String唁桩、StringBuffer闭树、StringBuilder有什么區(qū)別?這個(gè)問題在面試中經(jīng)常碰到荒澡,今天主要講解一下如何理解Java中的String报辱、StringBuffer、StringBuilder单山。
典型回答
String 是 Java 語(yǔ)言非嘲郑基礎(chǔ)和重要的類幅疼,提供了構(gòu)造和管理字符串的各種基本邏輯。它是典型的不可變類( Immutable )昼接,被聲明成為 final class爽篷,所有屬性也都是 final 的。也由于它的不可變性慢睡,類似拼接逐工、裁剪字符串等動(dòng)作,都會(huì)產(chǎn)生新的 String 對(duì)象漂辐。由于字符串操作的普遍性泪喊,所以相關(guān)操作的效率往往對(duì)應(yīng)用性能有明顯影響。
StringBuffer 是為解決上面提到拼接產(chǎn)生太多中間對(duì)象的問題而提供的一個(gè)類髓涯,我們可以用 append 或者 add 方法袒啼,把字符串添加到已有序列的末尾或者指定位置。StringBuffer 本質(zhì)是一個(gè)線程安全的可修改字符序列纬纪,它保證了線程安全蚓再,也隨之帶來了額外的性能開銷,所以除非有線程安全的需要育八,不然還是推薦使用它的后繼者,也就是 StringBuilder赦邻。
StringBuilder 是 Java 1.5 中新增的髓棋,在能力上和 StringBuffer 沒有本質(zhì)區(qū)別,但是它去掉了線程安全的部分惶洲,有效減小了開銷按声,是絕大部分情況下進(jìn)行字符串拼接的首選。
考點(diǎn)分析
幾乎所有的應(yīng)用開發(fā)都離不開操作字符串恬吕,理解字符串的設(shè)計(jì)和實(shí)現(xiàn)以及相關(guān)工具如拼接類的使用签则,對(duì)寫出高質(zhì)量代碼是非常有幫助的。關(guān)于這個(gè)問題铐料,前面的回答是一個(gè)通常的概要性回答渐裂,至少你要知道 String 是 Immutable 的,字符串操作不當(dāng)可能會(huì)產(chǎn)生大量臨時(shí)字符串钠惩,以及線程安全方面的區(qū)別柒凉。
如果繼續(xù)深入,面試官可以從各種不同的角度考察篓跛,比如可以:
- 通過 String 和相關(guān)類膝捞,考察基本的線程安全設(shè)計(jì)與實(shí)現(xiàn),各種基礎(chǔ)編程實(shí)踐愧沟。
- 考察 JVM 對(duì)象緩存機(jī)制的理解以及如何良好地使用蔬咬。
- 考察 JVM 優(yōu)化 Java 代碼的一些技巧鲤遥。
- String 相關(guān)類的演進(jìn),比如 Java 9 中實(shí)現(xiàn)的巨大變化林艘。
- ...
知識(shí)擴(kuò)展
字符串設(shè)計(jì)和實(shí)現(xiàn)考量
String 是 Immutable 類的典型實(shí)現(xiàn)盖奈,原生的保證了基礎(chǔ)線程安全,因?yàn)槟銦o法對(duì)它內(nèi)部數(shù)據(jù)進(jìn)行任何修改北启,這種便利甚至體現(xiàn)在拷貝構(gòu)造函數(shù)中卜朗,由于不可變,Immutable 對(duì)象在拷貝時(shí)不需要額外復(fù)制數(shù)據(jù)咕村。
我們?cè)賮砜纯?StringBuffer 實(shí)現(xiàn)的一些細(xì)節(jié)场钉,它的線程安全是通過把各種修改數(shù)據(jù)的方法都加上 synchronized 關(guān)鍵字實(shí)現(xiàn)的,非常直白懈涛。其實(shí)逛万,這種簡(jiǎn)單粗暴的實(shí)現(xiàn)方式,非常適合我們常見的線程安全類實(shí)現(xiàn)批钠,不必糾結(jié)于 synchronized 性能之類的宇植,有人說“過早優(yōu)化是萬惡之源”,考慮可靠性埋心、正確性和代碼可讀性才是大多數(shù)應(yīng)用開發(fā)最重要的因素指郁。
為了實(shí)現(xiàn)修改字符序列的目的,StringBuffer 和 StringBuilder 底層都是利用可修改的(char拷呆,JDK 9 以后是 byte)數(shù)組闲坎,二者都繼承了 AbstractStringBuilder,里面包含了基本操作茬斧,區(qū)別僅在于最終的方法是否加了 synchronized腰懂。
在具體的代碼書寫中,應(yīng)該如何選擇呢项秉?
在沒有線程安全問題的情況下绣溜,全部拼接操作是應(yīng)該都用 StringBuilder 實(shí)現(xiàn)嗎?畢竟這樣書寫的代碼娄蔼,還是要多敲很多字的怖喻,可讀性也不理想,下面的對(duì)比非常明顯岁诉。
String strByBuilder = new
StringBuilder().append("aa").append("bb").append("cc").append
("dd").toString();
String strByConcat = "aa" + "bb" + "cc" + "dd";
其實(shí)罢防,在通常情況下,沒有必要過于擔(dān)心唉侄,要相信 Java 還是非常智能的咒吐。
我們來做個(gè)實(shí)驗(yàn),把下面一段代碼,利用不同版本的 JDK 編譯恬叹,然后再反編譯候生,例如:
public class StringConcat {
public static void main(String[] args) {
String myStr = "aa" + "bb" + "cc" + "dd";
System.out.println("My String:" + myStr);
}
}
先編譯再反編譯
${JAVA_HOME}/bin/javac StringConcat.java
${JAVA_HOME}/bin/javap -v StringConcat.class
JDK 8 的輸出片段是:
0: ldc #2 // String hellojava!
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: new #4 // class java/lang/StringBuilder
9: dup
10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V
13: ldc #6 // String Concat String:
15: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: aload_1
19: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
25: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
28: return
你可以看到,在 JDK 8 中绽昼,字符串拼接操作會(huì)自動(dòng)被 javac 轉(zhuǎn)換為 StringBuilder 操作唯鸭,而在 JDK 9 里面則是因?yàn)?Java 9 為了更加統(tǒng)一字符串操作優(yōu)化,提供了 StringConcatFactory硅确,作為一個(gè)統(tǒng)一的入口目溉。javac 自動(dòng)生成的代碼,雖然未必是最優(yōu)化的菱农,但普通場(chǎng)景也足夠了缭付,你可以酌情選擇。
參考自極客時(shí)間:Java核心技術(shù)36講
感謝原作者:楊曉峰老師