对于String,Java Doc中描述是一旦被创建就不能被更改(“Strings are constant; their values cannot be changed after they are created”),任何对String对象的修改操作都会生成新的String对象。
了解String对象创建与拼接的一些内部细节,有利于更高效地对字符串进行操作。
String对象的创建
String对象有两种创建形式:
- 编译期能确定的字符串对象,会放到Java内存空间的常量池中,编译器首先检查常量池,是否已存在该字符串,若不存在,则先创建并指向,若存在则直接将引用指向那个字符串(对于内容相同的字符串仅保留一份)。
- 使用new关键词创建字符串对象,在堆上分配,运行期创建,与其他对象一样,即使内容相同也是不同的对象。
如下代码:
1 2 3 4 |
String s1 = "abc"; String s2 = "ab" + "c"; String s3 = new String("abc"); String s4 = "ab"; s4 += "c"; |
使用javap反编译为字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Code: 0: ldc #2 // String abc 2: astore_1 3: ldc #2 // String abc 5: astore_2 6: new #3 // class java/lang/String 9: dup 10: ldc #2 // String abc 12: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 15: astore_3 16: ldc #5 // String ab 18: astore 4 20: new #6 // class java/lang/StringBuilder 23: dup 24: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 27: aload 4 29: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: ldc #9 // String c 34: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 40: astore 4 42: return |
可以看到:
- 对于 s1 = "abc",s1指向常量池中的字符串”abc”;
- 对于 s2 = "ab" + "c",编译器进行了优化,s2同样指向了常量池中的字符串”abc”;
- 对于 s3 = new String("abc"),编译器使用new指令创建堆上的对象;
- 对于 s4 = "ab"; s4 += "c";,编译器首先将s4指向常量池中的对象”ab”,然后构建的StringBuilder对象拼接了”c”,返回的是堆上的对象。
那么:
- 对于 s1 == s2,值为 true;
- 对于 s1 == s3,值为 false;
- 对于 s1 == s4,值为 false;
- 对于 s3 == s4,值为 false。
String对象的拼接
如下代码:
1 2 3 4 |
String s1 = "ab" + "c"; String s2 = "a"; s2 += "b"; s2 += "c"; String s3 = "a"; s3 = s3 + "b"; s3 = s3 + "c"; String s4 = new StringBuffer().append("a").append("b").append("c").toString(); |
使用javap反编译为字节码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
Code: 0: ldc #2 // String abc 2: astore_1 3: ldc #3 // String a 5: astore_2 6: new #4 // class java/lang/StringBuilder 9: dup 10: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 13: aload_2 14: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 17: ldc #7 // String b 19: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: astore_2 26: new #4 // class java/lang/StringBuilder 29: dup 30: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 33: aload_2 34: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 37: ldc #9 // String c 39: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 42: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 45: astore_2 46: ldc #3 // String a 48: astore_3 49: new #4 // class java/lang/StringBuilder 52: dup 53: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 56: aload_3 57: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 60: ldc #7 // String b 62: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 65: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 68: astore_3 69: new #4 // class java/lang/StringBuilder 72: dup 73: invokespecial #5 // Method java/lang/StringBuilder."<init>":()V 76: aload_3 77: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 80: ldc #9 // String c 82: invokevirtual #6 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 85: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 88: astore_3 89: new #10 // class java/lang/StringBuffer 92: dup 93: invokespecial #11 // Method java/lang/StringBuffer."<init>":()V 96: ldc #3 // String a 98: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 101: ldc #7 // String b 103: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 106: ldc #9 // String c 108: invokevirtual #12 // Method java/lang/StringBuffer.append:(Ljava/lang/String;)Ljava/lang/StringBuffer; 111: invokevirtual #13 // Method java/lang/StringBuffer.toString:()Ljava/lang/String; 114: astore 4 116: return |
可以看到:
- 对于 s1 = "ab" + "c",编译器优化为”abc”直接放入常量池;
- 对于 s2 = "a"; s2 += "b"; s2 += "c";,每一个+=操作,都创建了一个新的StringBuilder对象;
- 对于 s3 = "a"; s3 = s3 + "b"; s3 = s3 + "c";,同s2;
- 对于 s4 = new StringBuffer().append("a").append("b").append("c").toString();,很明显只有一个StringBuilder对象。
所以,对于连续的字符串变量拼接操作,尤其是循环中的,要在循环外创建StringBuilder对象用于循环内的拼接,若使用+=运算符,每次循环都会创建新的StringBuilder对象,大大降低效率。
参考链接
http://docs.oracle.com/javase/7/docs/api/java/lang/String.html
http://www.cnblogs.com/hzmark/archive/2012/12/10/JavaString.html