1. 三者概述
1.1 String
(1)String是不可变的,其实就是代码中会修改字符串的方法都会返回一个新建的String对象,不会影响到原来的String对象。正因为String是不可变的,所以它对应的字符串字面量可以被共享,这应该也是字符串常量池能够存在的一个原因
(2)Java中的所有字符串字面量都被当作String的实例进行处理
(3)String的"+"运算符会转化为StringBuilder或StringBuffer进行处理
1.2 StringBuilder和StringBuffer
(1)相同点
- 二者都是可变的,其实就是代码中返回的是this,而不是新建StringBuilder或StringBuffer对象并返回
- 二者都继承自AbstractStringBuilder,AbstractStringBuilder.value没有被final修饰(String.value是被final修饰的),即AbstractStringBuilder.value的引用是可变的,这是扩容的前提
- 既然二者是可变的,那么随着字符串的添加(通过append方法),容量会不足,所以会在Arrays.copyOf中新建指定大小的数组,并利用System.arraycopy进行数据的拷贝,最后AbstractStringBuilder.value会指向这个新数组
(2)不同点
二者几乎相同,不同点主要有两个:
- StringBuilder是线程不安全的,StringBuffer是线程安全的,因为StringBuffer中除了构造方法,其余几乎所有方法都被synchronized修饰,没有被synchronized修饰在方法内最终也会调用到有synchronized的重载方法
- StringBuffer比StringBuilder多一个toStringCache字段,用作缓存
2. 下面从字节码层面对三者字符串拼接操作的性能进行比较
2.1 String
(1)源码示例
public static void main(String[] args) {
String s = "";
for (int i = 0; i < 100; i++) {
s += "a";
}
}
(2)反编译后的字节码指令
// 0~2:与String s = ""对应
0: ldc #2 // String
2: astore_1
// 3~34:与整个for循环对应
// 3~4:与int i = 0对应
3: iconst_0
4: istore_2
// 5~6:与i < 100对应
5: iload_2
6: bipush 100
8: if_icmpge 37
// 11~30:与s += "a"对应,但转成了下面几步:
// step1:创建StringBuilder对象
11: new #3 // class java/lang/StringBuilder
14: dup
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
// step2:将s推送至栈顶调用StringBuilder.append(s)
18: aload_1
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
// step3:将"a"推送至栈顶调用StringBuilder.append("a")
22: ldc #6 // String a
24: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
// step4:调用StringBuilder.toString()
27: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
// step5:将StringBuilder.toString()的结果存入s中
30: astore_1
// 31:与i++对应
31: iinc 2, 1
34: goto 5
37: return
从字节码指令中可得,for循环中String的"+"运算符会转换为StringBuilder(JDK 5之前会转为StringBuffer,JDK 5及之后,会转为StringBuilder)进行处理,且每次循环都会创建新的StringBuilder,会影响性能。
2.2 StringBuilder
(1)源码示例
public static void main(String[] args) {
String s1;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 100; i++) {
builder.append("a");
}
s1 = builder.toString();
}
(2)反编译后的字节码指令
// 0~7:与StringBuilder builder = new StringBuilder()对应
0: new #2 // class java/lang/StringBuilder
3: dup
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
7: astore_2
// 8~26:与整个for循环对应
// 8~9:与int i = 0对应
8: iconst_0
9: istore_3
// 10~13:与i < 100对应
10: iload_3
11: bipush 100
13: if_icmpge 29
// 16~22:与builder.append("a")对应
16: aload_2
17: ldc #4 // String a
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: pop
// 23:与i++对应
23: iinc 3, 1
26: goto 10
// 29~33:与s1 = builder.toString()对应
29: aload_2
30: invokevirtual #6 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
33: astore_1
34: return
2.1和2.2中完成的功能是相同的,但从字节码指令中可得,2.2中StringBuilder只创建了一次。因此,在某些字符串拼接的场景下,用StringBuilder可以提高性能。
2.3 StringBuffer
(1)源码示例
public static void main(String[] args) {
String s2;
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 100; i++) {
buffer.append("a");
}
s2 = buffer.toString();
}
(2)反编译后的字节码指令
与2.2(2)中的字节码指令完全一致(不同点是常量池中的StringBuilder会变成StringBuffer),但由于StringBuffer的方法由synchronized修饰,因此,它的拼接操作在速度上比StringBuilder慢,但比String快。