先来看两段段简短的代码:
示例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);
}
</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。