Java中的String、StringBuffer、StringBuilder有什麼區別?
String、StringBuffer、StringBuilder有什麼區別?這個問題在面試中經常碰到,今天主要講解一下如何理解Java中的String、StringBuffer、StringBuilder 。
典型回答
String是 Java 語言非常基礎和重要的類,提供了構造和管理字串的各種基本邏輯。它是典型的不可變類 ( Immutable ),被宣告成為final class ,所有屬性也都是 final 的。也由於它的不可變性,類似拼接、裁剪字串等動作,都會產生新的 String 物件。由於字串操作的普遍性,所以相關操作的效率往往對應用效能有明顯影響。
StringBuffer是為解決上面提到拼接產生太多中間物件的問題而提供的一個類,我們可以用 append 或者 add 方法,把字串新增到已有序列的末尾或者指定位置。StringBuffer 本質是一個執行緒安全 的可修改字元序列,它保證了執行緒安全,也隨之帶來了額外的效能開銷,所以除非有執行緒安全的需要,不然還是推薦使用它的後繼者,也就是 StringBuilder。
StringBuilder是 Java 1.5 中新增的,在能力上和 StringBuffer 沒有本質區別,但是它去掉了執行緒安全 的部分,有效減小了開銷,是絕大部分情況下進行字串拼接的首選 。
考點分析
幾乎所有的應用開發都離不開操作字串,理解字串的設計和實現以及相關工具如拼接類的使用,對寫出高質量程式碼是非常有幫助的。關於這個問題,前面的回答是一個通常的概要性回答,至少你要知道String 是 Immutable 的,字串操作不當可能會產生大量臨時字串,以及執行緒安全方面的區別 。
如果繼續深入,面試官可以從各種不同的角度考察,比如可以:
- 通過 String 和相關類,考察基本的執行緒安全設計與實現,各種基礎程式設計實踐。
- 考察 JVM 物件快取機制的理解以及如何良好地使用。
- 考察 JVM 優化 Java 程式碼的一些技巧。
- String 相關類的演進,比如 Java 9 中實現的巨大變化。
- ...
知識擴充套件
字串設計和實現考量
String 是 Immutable 類的典型實現,原生的保證了基礎執行緒安全,因為你無法對它內部資料進行任何修改,這種便利甚至體現在拷貝建構函式中,由於不可變,Immutable 物件在拷貝時不需要額外複製資料。
我們再來看看 StringBuffer 實現的一些細節,它的執行緒安全是通過把各種修改資料的方法都加上 synchronized 關鍵字實現的,非常直白。其實,這種簡單粗暴的實現方式,非常適合我們常見的執行緒安全類實現,不必糾結於 synchronized 效能之類的,有人說“過早優化是萬惡之源”,考慮可靠性、正確性和程式碼可讀性才是大多數應用開發最重要的因素。
為了實現修改字元序列的目的,StringBuffer 和 StringBuilder 底層都是利用可修改的(char,JDK 9 以後是 byte)陣列,二者都繼承了 AbstractStringBuilder,裡面包含了基本操作,區別僅在於最終的方法是否加了 synchronized 。
在具體的程式碼書寫中,應該如何選擇呢?
在沒有執行緒安全問題的情況下,全部拼接操作是應該都用 StringBuilder 實現嗎?畢竟這樣書寫的程式碼,還是要多敲很多字的,可讀性也不理想,下面的對比非常明顯。
String strByBuilder= new StringBuilder().append("aa").append("bb").append("cc").append ("dd").toString(); String strByConcat = "aa" + "bb" + "cc" + "dd";
其實,在通常情況下,沒有必要過於擔心,要相信 Java 還是非常智慧的。
我們來做個實驗,把下面一段程式碼,利用不同版本的 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 中,字串拼接操作會自動被 javac 轉換為 StringBuilder 操作,而在 JDK 9 裡面則是因為 Java 9 為了更加統一字串操作優化,提供了 StringConcatFactory,作為一個統一的入口。javac 自動生成的程式碼,雖然未必是最優化的,但普通場景也足夠了,你可以酌情選擇。