String中的字串拼接問題
先來看兩段段簡短的程式碼:
示例1:
示例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);
}
由反編譯程式碼可以看出,程式碼 ```java s += "abc"; ``` 等價於 ```java StringBuilder tmp = new StringBuilder(); tmp.append(String.valueOf((Object)null)); tmp.append("abc"); s = tmp.toString();</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
而`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。