String , StringBuffer , StringBuilder的區別

String ,  StringBuffer ,  StringBuilder的區別

String

首先,String 是用來表示一個字符串常量的,它是一個不可變對象,意味着,一旦咱們建立了某個字符串以後,就不能再改變它的值了,咱們能夠從它的源碼中看到,它是使用一個 final 的數組來存放內容的,即表示它是一個常量。html

/** The value is used for character storage. */
    private final char value[];

接下來看個例子:java

public class Main {

    public static void main(String[] args) {

        String s1 = "a";
        String s2 = "a";
        String s3 = new String("a");
        System.out.println(s1 == s2); // true
        System.out.println(s1 == s3); // false

        String s4 = new String("b");
        String s5 = new String("b");
        System.out.println(s4 == s5); // false

        String s6 = "a" + "b";
        String s7 = "ab";
        System.out.println(s6 == s7); // true

        String s8 = new String("ab");
        System.out.println(s6 == s8); // false
        System.out.println(s7 == s8); //false
    }
}

由於 String 是存放在常量池中的,虛擬機會對其進行優化,上面,雖然聲明瞭 8 個變量,但是在常量池中只存放了 "a","b","ab" 這三個常量,使用 jclasslib 查看可知,以下所示:數組

聲明一個字符串對象,若是能在常量池中找到,則直接把引用指向它便可,找不到纔會建立,以後放到常量池中。安全

因爲,字符串是不可變對象,若是多個字符串相加,則會出現什麼呢?多線程

String s1 = "a" + "b" + "c" + "d" + "e";
        System.out.println(s1);

分析字節碼:oracle

能夠看到,上述字符串相加後,JVM仍是一次性把 「abcde」加載到內存中,此時常量池中只有一個字符串變量,即 "abcde",以下所示:app

ldc指令:把字符串加載到常量池中(Push item from run-time constant pool),更多JVM指令參考:JVM指令jvm

也就是說 經過 "a" + "b" + "c" + "d" + "e" 和 "abcde" 對於 JVM 來講是一回事,只會加載一次;性能

可是經過下面這種方式相加會有什麼變化呢?優化

String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        String s4 = "d";
        String s5 = "e";
        String s6 = s1 + s2 + s3 + s4 + s5;
        System.out.println(s6);

首先,看下,JVM調用幾回 ldc 指令加載字符串到常量池中:

再來看看這種方式一個所有的字節碼:

0 ldc #2 <a>
 2 astore_1
 3 ldc #3 <b>
 5 astore_2
 6 ldc #4 <c>
 8 astore_3
 9 ldc #5 <d>
11 astore 4
13 ldc #6 <e>
15 astore 5
17 new #7 <java/lang/StringBuilder>
20 dup
21 invokespecial #8 <java/lang/StringBuilder.<init>>
24 aload_1
25 invokevirtual #9 <java/lang/StringBuilder.append>
28 aload_2
29 invokevirtual #9 <java/lang/StringBuilder.append>
32 aload_3
33 invokevirtual #9 <java/lang/StringBuilder.append>
36 aload 4
38 invokevirtual #9 <java/lang/StringBuilder.append>
41 aload 5
43 invokevirtual #9 <java/lang/StringBuilder.append>
46 invokevirtual #10 <java/lang/StringBuilder.toString>
49 astore 6

前面部分,調用 ldc 指令加載字符串到常量池中,後面部分能夠看到,JVM 會對這種字符串相加的方式進行優化,使用 StringBuilder 來進行字符串的拼接,

StringBuffer

StringBuffer 它是一個可變字符串,從源碼中能夠看到,它用來存放元素的 char 數組沒有使用 final 修飾,且,char 數組的初始大小爲 16.:

StringBuffer sb = new StringBuffer();
                |
                |
    public StringBuffer() {
        super(16);
    }
                |
                |
    AbstractStringBuilder(int capacity) {
        value = new char[capacity];
    }
                |
                |
         char[] value;

以後使用它的 append 方法進行字符串的鏈接:

String s1 = "a";
        String s2 = "b";
        String s3 = "c";
        String s4 = "d";
        String s5 = "e";

        StringBuffer sb = new StringBuffer();
        sb.append(s1).append(s2).append(s3).append(s4).append(s5);

字節碼以下:

0 ldc #2 <a>
 2 astore_1
 3 ldc #3 <b>
 5 astore_2
 6 ldc #4 <c>
 8 astore_3
 9 ldc #5 <d>
11 astore 4
13 ldc #6 <e>
15 astore 5
17 new #7 <java/lang/StringBuffer>
20 dup
21 invokespecial #8 <java/lang/StringBuffer.<init>>
24 astore 6
26 aload 6
28 aload_1
29 invokevirtual #9 <java/lang/StringBuffer.append>
32 aload_2
33 invokevirtual #9 <java/lang/StringBuffer.append>
36 aload_3
37 invokevirtual #9 <java/lang/StringBuffer.append>
40 aload 4
42 invokevirtual #9 <java/lang/StringBuffer.append>
45 aload 5
47 invokevirtual #9 <java/lang/StringBuffer.append>
50 pop
51 return

此外,StringBuffer 仍是線程安全的,可在多線程下使用,append 方法被 synchronized 修飾,以保證同步:

public synchronized StringBuffer append(Object obj) {
        toStringCache = null;
        super.append(String.valueOf(obj));
        return this;
    }

StringBuilder

StringBuilder 它和 StringBuffer 同樣,都是可變字符串,它用來存放元素的 char 數組沒有使用 final 修飾,且,char 數組的初始大小爲 16.:

StringBuilder sb = new StringBuilder();
               |
               |
    public StringBuilder() {
        super(16);
    }
               |
               |
 AbstractStringBuilder(int capacity) {
        value = new char[capacity];
 }
               |
               |
          char[] value;

其實,StringBuilder 和 StringBuffer 它們有共同的父類:

只不過 StringBuilder 不是線程安全的,在多線程環境下使用會出現數據不一致的問題。它的 append 方法並無使用 synchronized 修飾:

public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

它經過 append 拼接字符串的字節碼和 StringBuffer 的同樣。

總結

綜上,

String 它是一個不可變的對象,一旦建立就不會改變,String 的相加,JVM 會調用 StringBuilder 的 append 來進行優化。

StringBuffer它是可變字符串,它是線程安全的。

StringBuilder它也是可變字符串,可是它不是線程安全的,它和 StringBuffer 有共同的父類。

因爲 StringBuffer 的 append 方法有 synchronized 進行修飾,因此性能較 StringBuilder 的低,若是在單線程下,可以使用 StringBuilder。

相關文章
相關標籤/搜索