1.StringBuilder
我们在对做字符串拼接时,如果采用如下操作:
String result = "";
for(String str : strArr){
result += str;
}
确实能够达到拼接字符串的目的,但是会在字符串常量池中创建多个字符串对象,会浪费大量的内存,并且影响GC(垃圾收集)效率。为了解决这个问题,Java标准库提供了StringBuilder类以实现字符串拼接。StringBuilder与String不同,它的对象是可变对象,并且会预分配缓冲区,如此就不会创建多个对象,只用创建一个StringBuilder对象即可。
一般情况下,对于字符串间的+
操作,并不需要用StringBuilder。因为Java编译时会把字符串+
操作编译为StringConcatFactory的操作,自动将字符串拼接优化为数组复制或StringBuilder的操作。
2.从下面两道面试题展开讨论:
1
String str = "a" + "b";
此条语句创建了多少个对象?
2String str = new String("a" + "b") + "a" + "b";
此条语句创建了多少个对象?
第一条语句,Java编译时会将"a"+"b"
直接编译为"ab"
,如此原语句等同于String str = "ab";
,这样会在字符串常量池中创建一个字符串对象。
对于本例,我们编写代码并使用javap进行解析:
public class cp1 {
public static void main(String[] args){
String s = "a" + "b";
System.out.println(s);
}
}
使用javac -g cp1.java
编译源码之后,使用javap -v cp1
查看解析结果:
Classfile /E:/Java学习/src/cp1.class
Last modified 2021-10-19; size 544 bytes
MD5 checksum e771532b8aacc4a59518aae9ac7faddd
Compiled from "cp1.java"
public class cp1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#22 // java/lang/Object."":()V
#2 = String #23 // ab
#3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream;
#4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #28 // cp1
#6 = Class #29 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lcp1;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 SourceFile
#21 = Utf8 cp1.java
#22 = NameAndType #7:#8 // "":()V
#23 = Utf8 ab
#24 = Class #30 // java/lang/System
#25 = NameAndType #31:#32 // out:Ljava/io/PrintStream;
#26 = Class #33 // java/io/PrintStream
#27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V
#28 = Utf8 cp1
#29 = Utf8 java/lang/Object
#30 = Utf8 java/lang/System
#31 = Utf8 out
#32 = Utf8 Ljava/io/PrintStream;
#33 = Utf8 java/io/PrintStream
#34 = Utf8 println
#35 = Utf8 (Ljava/lang/String;)V
{
public cp1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcp1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: ldc #2 // String ab
2: astore_1
3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_1
7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
10: return
LineNumberTable:
line 13: 0
line 14: 3
line 15: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 args [Ljava/lang/String;
3 8 1 s Ljava/lang/String;
}
SourceFile: "cp1.java"
可以看到在Constant pool中新创建了一个字符串对象"ab"
,在堆中并没有创建。总计只创建了一个字符串对象。
第二条语句,new String("a" + "b") + "a" + "b";
中,同样的编译器将"a"+"b"
直接编译为"ab"
,会在字符串常量池中创建"ab"
一个字符串对象,然后在堆中创建一个"ab"
字符串对象。然后调用StringBuilder的append()方法拼接字符串后,调用toString()方法返回了一个字符串对象"abab"
。
对第二例采用相同的方法进行解析:
// 源码
public class cp1 {
String s = new String("a" + "b") + "a" + "b";
System.out.println(s);
}
}
// javap 解析结果
Classfile /E:/Java学习/src/cp1.class
Last modified 2021-10-19; size 746 bytes
MD5 checksum 4d1188a58dd3d189626ca06c1a5f9e18
Compiled from "cp1.java"
public class cp1
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #12.#28 // java/lang/Object."":()V
#2 = Class #29 // java/lang/StringBuilder
#3 = Methodref #2.#28 // java/lang/StringBuilder."":()V
#4 = Class #30 // java/lang/String
#5 = String #31 // ab
#6 = Methodref #4.#32 // java/lang/String."":(Ljava/lang/String;)V
#7 = Methodref #2.#33 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #2.#34 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#10 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #39 // cp1
#12 = Class #40 // java/lang/Object
#13 = Utf8
#14 = Utf8 ()V
#15 = Utf8 Code
#16 = Utf8 LineNumberTable
#17 = Utf8 LocalVariableTable
#18 = Utf8 this
#19 = Utf8 Lcp1;
#20 = Utf8 main
#21 = Utf8 ([Ljava/lang/String;)V
#22 = Utf8 args
#23 = Utf8 [Ljava/lang/String;
#24 = Utf8 s
#25 = Utf8 Ljava/lang/String;
#26 = Utf8 SourceFile
#27 = Utf8 cp1.java
#28 = NameAndType #13:#14 // "":()V
#29 = Utf8 java/lang/StringBuilder
#30 = Utf8 java/lang/String
#31 = Utf8 ab
#32 = NameAndType #13:#41 // "":(Ljava/lang/String;)V
#33 = NameAndType #42:#43 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#34 = NameAndType #44:#45 // toString:()Ljava/lang/String;
#35 = Class #46 // java/lang/System
#36 = NameAndType #47:#48 // out:Ljava/io/PrintStream;
#37 = Class #49 // java/io/PrintStream
#38 = NameAndType #50:#41 // println:(Ljava/lang/String;)V
#39 = Utf8 cp1
#40 = Utf8 java/lang/Object
#41 = Utf8 (Ljava/lang/String;)V
#42 = Utf8 append
#43 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#44 = Utf8 toString
#45 = Utf8 ()Ljava/lang/String;
#46 = Utf8 java/lang/System
#47 = Utf8 out
#48 = Utf8 Ljava/io/PrintStream;
#49 = Utf8 java/io/PrintStream
#50 = Utf8 println
{
public cp1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcp1;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."":()V
7: new #4 // class java/lang/String
10: dup
11: ldc #5 // String ab
13: invokespecial #6 // Method java/lang/String."":(Ljava/lang/String;)V
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #5 // String ab
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore_1
28: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
31: aload_1
32: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
35: return
LineNumberTable:
line 13: 0
line 14: 28
line 15: 35
LocalVariableTable:
Start Length Slot Name Signature
0 36 0 args [Ljava/lang/String;
28 8 1 s Ljava/lang/String;
}
SourceFile: "cp1.java"
可以看到在常量池中创建了一个字符串对象"ab"
,在堆中创建了一个StringBuilder对象,一个字符串对象"ab"
,并且调用StringBuilder对象的toString()方法返回了一个字符串对象"abab"
。总计创建了三个字符串对象,一个StringBuilder对象。