前言
据我所知字符串确实已经成为 Java 开发人员最常用的类了,而且是大量使用。我们都知道,String 其实是封装了字符,所以俩字符串连接就是将字符串对象里面的字符连起来。很多人习惯使用+来连接字符串,也有人会用 StringBuilder 的append方法。
"+"编译后
看看如果我们在程序中直接使用+来连接字符串的情况,用下面一个简单的例子来说明,进行两个字符串连接操作,即s3 = s1 + s2。
public class TestString { public static void main(String[] args) { String s1 = "www"; String s2 = "ccc"; String s3 = s1 + s2; } }
接着javap -c TestString.class看一下编译后的情况,可以看到编译器其实是对+进行了转换的,转成了 StringBuilder 对象来操作了,首先使用 s1 创建 StringBuilder 对象,然后用 append方法连接 s2,最后调用toString方法完成。
public class com.seaboat.string.TestString { public com.seaboat.string.TestString(); 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 www 2: astore_1 3: ldc #18 // String ccc 5: astore_2 6: new #20 // class java/lang/StringBuilder 9: dup 10: aload_1 11: invokestatic #22 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 14: invokespecial #28 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 17: aload_2 18: invokevirtual #31 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 21: invokevirtual #35 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 24: astore_3 25: return }
"+"与"append"等价吗
前面可以看到+在编译器作用下都会转成 StringBuilder 的append方法执行,所以如果抛开运行效率来说,它们其实本质是一样的。
本质一样是否就能说明它们时等价的呢?或者说能否为了方便直接用+来连接字符串,剩下的事就交给编译器了?继续看个例子,在这个例子中有个 for 循环进行字符串连接操作。
public class TestString2 { public static void main(String[] args) { String s = "www"; for (int i = 0; i < 10; i++) s += i; } }
编译后的情况如下,不熟悉指令没关系,我们只看重要的部分,if_icmplt 8,这个就是 for 循环的条件判断,小于10则不断跳到8的位置,8后面其实就是创建 StringBuilder 对象,并以本地变量s的值初始化该对象,接着再将本地变量i append到 StringBuilder 对象中,最后调用toString方法将所得值存到本地变量s。
这样来看循环中每次都要创建 StringBuilder 对象,而且要调用toString方法,这样的执行效率显然比较低,而且增加了 GC 的压力。
public class com.seaboat.string.TestString2 { public com.seaboat.string.TestString2(); 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 www 2: astore_1 3: iconst_0 4: istore_2 5: goto 30 8: new #18 // class java/lang/StringBuilder 11: dup 12: aload_1 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: iload_2 20: invokevirtual #29 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 23: invokevirtual #33 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 26: astore_1 27: iinc 2, 1 30: iload_2 31: bipush 10 33: if_icmplt 8 36: return }
友好写法
把事情都丢给编译器是不友好的,为了能让程序执行更加高效,最好是我们自己来控制 StringBuilder 的实例,比如下面,只创建一个 StringBuilder 实例,后面用append方法连接字符串。
public class TestString3 { public static void main(String[] args) { StringBuilder sb = new StringBuilder("www"); for (int i = 0; i < 10; i++) sb.append(i); } }
编译后的情况如下,首先创建一个 StringBuilder 对象,使用字符串"www"来实例化该对象,接着循环调用append方法将本地变量i添加到 StringBuilder 对象中。
public class com.seaboat.string.TestString3 { public com.seaboat.string.TestString3(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #16 // class java/lang/StringBuilder 3: dup 4: ldc #18 // String www 6: invokespecial #20 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 9: astore_1 10: iconst_0 11: istore_2 12: goto 24 15: aload_1 16: iload_2 17: invokevirtual #23 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 20: pop 21: iinc 2, 1 24: iload_2 25: bipush 10 27: if_icmplt 15 30: return }