1. 程式人生 > >String中的字串拼接問題

String中的字串拼接問題

先來看兩段段簡短的程式碼:
示例1

```java public String nullStringTest(){ String s = null; s += "abc"; return s; } ```

示例2

```java public void stringPool(){ String a="hello"; String b="hell"; String c=b+"o"; String d="hell"+"o";
System.out.println(a == b+new String("o"));
System.out.println(a == c);
System.out.println(a == d);

}

</textarea>
列印結果各是什麼?

##一、String + String 的本質

***示例1***的反編譯結果如下:

>注:java的反編譯指令 *javap  -verbose*  *類名.class*

 <textarea readonly="readonly" name="code" class="asm">
```asm
0: aconst_null
1: astore_1
2: new           #5       // class java/lang/StringBuilder
5: dup
6: invokespecial #6      // Method java/lang/StringBuilder."<init>":()V
9: aload_1
10: invokevirtual #7     // Method java/lang/StringBuilder.append:      (Ljava/lang/String;)Ljava/lang/StringBuilder;
13: ldc           #8     // String abc
15: invokevirtual #7     // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
18: invokevirtual #9   // Method java/lang/StringBuilder.toString:   	()Ljava/lang/String;
21: astore_1
22: aload_1
23: areturn
由反編譯程式碼可以看出,程式碼 ```java s += "abc"; ``` 等價於 ```java StringBuilder tmp = new StringBuilder(); tmp.append(String.valueOf((Object)null)); tmp.append("abc"); s = tmp.toString();
而`String`的`valueOf(Object ojb)`方法實現原始碼如下:
![這裡寫圖片描述](https://img-blog.csdn.net/20170920105707037?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3Vnd3VoYW4yMDE0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
因此***示例1***的結果為`nullabc`
![這裡寫圖片描述](https://img-blog.csdn.net/20170920105856637?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY3Vnd3VoYW4yMDE0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)

##二、String物件的儲存
***示例2***的反編譯程式碼比較長,就不貼出了,有興趣的朋友自行反編譯出來研讀。在分析***示例2***的結果之前,先來看一個更簡單的***示例3***:
```java
String str1 = "abc";
String str2 = new String("abcd");

***示例3***的記憶體分配情況如下圖:
這裡寫圖片描述
String str1 = "abc";先有字串"abc"存在於常量池,然後Java棧上的str1執行常量池字串"abc"
String str2 = new String("abcd");先有字串"abcd"放入常量池,然後new了一份字串"abcd"放入Java堆(字串常量"abcd"在編譯期就已經確定放入常量池,而Java堆上的"abcd"是在執行期初始化階段才確定,因此先有常量池"abcd",再有Java堆”abcd”),然後Java棧的str2指向Java堆的"abcd"
如果***示例3***的記憶體分配可以理解,就不難得出***示例2***的記憶體分配情況:
這裡寫圖片描述
***示例2***的執行結果為:
這裡寫圖片描述
思考:String類為何設計為不可變?

##三、String、StringBuild、StringBuffer的區別
String是字串常量的引用,String += String的本質是new了新的臨時物件StringBuild,拼接後再StringBuild.toString賦給原String。所有大量字串拼接不要直接使用String,否則會生成大量臨時物件,嚴重影響效能。
StringBuild進行字串拼接不會生成臨時物件,效率高,但不是執行緒安全的。
StringBuffer進行字串拼接也不會生成臨時物件,效率略低於StringBuild,執行緒安全。
***示例4***是以上三種類進行大量字串拼接的示例程式碼:

private static int LOOP_TIMES = 10000;
private Random ran = new Random();

public void loopString(){
    String string = "";
    for (int i=0; i<LOOP_TIMES; i++){
        string += ran.nextInt();
    }
}

public void loopStringBuild(){
    StringBuilder stringBuilder = new StringBuilder();
    for (int i=0; i<LOOP_TIMES; i++){
        stringBuilder.append(ran.nextInt());
    }
}

public void loopStringBuffer(){
    StringBuffer stringBuffer = new StringBuffer();
    for (int i=0; i<LOOP_TIMES; i++){
        stringBuffer.append(ran.nextInt());
    }
}

執行結果:

I/MainActivity: loopString -->18435ms
I/MainActivity: loopStringBuild -->10ms
I/MainActivity: loopStringBuffer -->10ms

##總結
String的+操作是一種語法糖,其本質是建立了臨時的StringBuild物件進行append操作,然後toString()賦給原來的String引用,因此大量字串拼接不要直接用String,應該使用StringBuild或StringBuffer,其中StringBuild不考慮執行緒同步,效率更高,StringBuffer考慮執行緒安全,效率略低於StringBuild。