字符串操作是计算机程序设计中最常见的行为
1.不可变的String
String对象是不可变的。String类中每一个看起来会修改String值的方法,实际上都只是简单粗暴的创建一个全新的String对象,而最初的String对象是丝毫不动的。
示例:
package thinkingstring;
public class NoChangeStringTest {
public static void main(String[] args) {
String oldString = "oldString";
System.out.println(oldString);
String newString = oldString.toUpperCase();
System.out.println("oldString = " + oldString + " newString = " + newString);
}
}
运行结果
oldString
oldString = oldString newString = OLDSTRING
首先,现在虚拟机常量池中创建“oldString”这个字符串常量,然后把引用复制给oldString这个变量,然后打印输出,同理,把oldString引用的常量大写创建一个全新的变量,然后赋值给newString。
1.1不可变性的带来的效率问题
看如下代码:
public static void main(String[] args) {
String mango = "mango";
String s = "abc" + mango + "def" + 47;
System.out.println(s);
}
可以想象一下,String可能有一个append()方法,他会生成一个新的String对象,以包含“abc”与mango连接后的字符串。然后再与“def”相连,生成一个新的String对象,以此类推。这个方式按理来讲可行,但是这样会产生一大堆垃圾.
我们来看看实际情况是怎样的,通过Javap来反编译代码进行查看得到如下结果
Compiled from "NoChangeStringTest.java"
public class thinkingstring.NoChangeStringTest {
public thinkingstring.NoChangeStringTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: ldc #16 // String mango
2: astore_1
3: new #18 // class java/lang/StringBuilder
6: dup
7: ldc #20 // String abc
9: invokespecial #22 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
12: aload_1
13: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
16: ldc #29 // String def
18: invokevirtual #25 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: bipush 47
23: invokevirtual #31 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
26: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
29: astore_2
30: getstatic #38 // Field java/lang/System.out:Ljava/io/PrintStream;
33: aload_2
34: invokevirtual #44 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
37: return
}
由上可知编译器自动引入了StringBuilder类,我们在源代码并没有使用这个类,在这个例子中,编译器创建了一个StringBuilder对象,用以构造最终的String,并为每个字符串调用一次StringBuilder的append()方法,最后调用toString生成结果,并存在s中。
现在你是不是觉得可以随意使用String对象,反正编译器会帮你进行优化,没错我也是这样想的。but...我们更深入的看看编译器能为我们优化到什么程度。下面采取两种方式生成同一个String进行检验,一使用多个String对象,二在代码中使用StringBuilder。
public String implicit(String[] fields) {
String result = "";
for(int i = 0;i < fields.length;i++) {
result += fields[i];
}
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(int i = 0;i < fields.length;i++) {
result.append(fields[i]);
}
return result.toString();
}
反编译结果
Compiled from "NoChangeStringTest.java"
public class thinkingstring.NoChangeStringTest {
public thinkingstring.NoChangeStringTest();
Code:
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #16 // String
2: astore_2
3: iconst_0
4: istore_3
5: goto 32
8: new #18 // class java/lang/StringBuilder
11: dup
12: aload_2
13: invokestatic #20 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
16: invokespecial #26 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
19: aload_1
20: iload_3
21: aaload
22: invokevirtual #29 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #33 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
28: astore_2
29: iinc 3, 1
32: iload_3
33: aload_1
34: arraylength
35: if_icmplt 8
38: aload_2
39: areturn
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #18 // class java/lang/StringBuilder
3: dup
4: invokespecial #45 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
8: iconst_0
9: istore_3
10: goto 24
13: aload_2
14: aload_1
15: iload_3
16: aaload
17: invokevirtual #29 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: pop
21: iinc 3, 1
24: iload_3
25: aload_1
26: arraylength
27: if_icmplt 13
30: aload_2
31: invokevirtual #33 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
34: areturn
}
可以看到如果使用String对象的话,就会出现每次循环创建一次StringBuilder的情况,而使用StringBuilder的话,只会创建一次。所以拼接复杂的字符串的时候还是用StringBuilder比较好,相对简单的可以交给String。
顺带一提,StringBuilder是Java SE5引入的,在这之前Java用的是StringBuffer。后则是线程安全,因此开销会大点。