for循环中为什么最好使用StringBuilder?
此次操作都是在以下环境中
环境 | 版本 |
---|---|
archlinux | 4.16.13-1-ARCH |
jdk | java version "1.8.0_172" |
先看不使用StringBuilder的情况
public class Test {
public static void testForStr(){
String str = "";
for (int i=0; i<10; i++){
str += i;
}
}
public static void main(String[] args){
Test.testForStr();
}
}
为了更好的分析编译器到底干了什么,我们需要使用javap命令进行字节码分析,终端执行以下命令:
javac Test.java
javap -c Test.class
屏幕输出的结果为:
Compiled from "Test.java"
public class top.freesh.laughably.Test {
public top.freesh.laughably.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void testForStr();
Code:
//从常量池引用#2并推向栈顶
0: ldc #2 // String abc
//将栈顶的引用存入第一个局部变量
2: astore_0
//将int型常量0推入栈顶
3: iconst_0
//将栈顶0存入第二个int型变量中
4: istore_1
//第二个int局部变量进栈
5: iload_1
//将byte型常量10推入栈顶
6: bipush 10
//如果栈顶两个值大于等于0(此时0-10)则跳转36(code)
8: if_icmpge 36
//创建StringBuilder对象,其引用进栈
11: new #3 // class java/lang/StringBuilder
//复制栈顶引用并进栈(此时栈顶有两个对象的引用)
14: dup
//调用StringBuilder的构造方法(消耗掉一个对象引用)
15: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V
//第一个引用变量入栈(第一次为:abc)
18: aload_0
//调用append方法,消耗一个对象引用和一个abc,并返回一个对象引用存入栈顶(注意到append()方法返回的是StringBuilder引用)
19: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
//第二个int型变量入栈(0入栈,此处对应循环体中的i)
22: iload_1
//调用append方法,消耗一个对象引用和o,并返回一个对象引用存入栈顶(同19)
23: invokevirtual #6 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
//调用toString方法,并将产生的String存入栈顶
26: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
//将栈顶String存入本地第一个引用变量中
29: astore_0
//将本地第二个int型变量增加1
30: iinc 1, 1
//无条件跳转到5(code)
33: goto 5
//返回
36: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #8 // Method testForStr:()V
3: return
}
可见for循环中,对字符串进行加法操作,都会产生一个StringBuilder对象,这虽然减少了jvm常量池的压力,但也无疑增加了jvm中新生代的压力(增加了垃圾回收的几率)
再来看看使用StringBuilder的情况吧
public class Test {
public static void testForStr(){
//和上边的代码逻辑一样,只是这里使用StringBuilder连接字符串
StringBuilder stringBuilder = new StringBuilder("abc");
for (int i=0; i<127; i++){
stringBuilder.append(i);
}
}
public static void main(String[] args){
Test.testForStr();
}
}
使用同样的命令分析jvm指令
javac Test.java
javap -c Test.class
Compiled from "Test.java"
public class top.freesh.laughably.Test {
public top.freesh.laughably.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void testForStr();
Code:
0: new #2 // class java/lang/StringBuilder
3: dup
4: ldc #3 // String abc
6: invokespecial #4 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
//存储的是StringBuilder引用
9: astore_0
10: iconst_0
11: istore_1
12: iload_1
13: bipush 10
15: if_icmpge 30
18: aload_0
19: iload_1
20: invokevirtual #5 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
//此处将append产生的对象引用(此时位于栈顶)出栈,因为aload_0每次都会将引用入栈
23: pop
24: iinc 1, 1
27: goto 12
30: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #6 // Method testForStr:()V
3: return
}
完全没有问题,每次循环也不会产生StringBuilder对象了
建议大家以后在循环中使用StringBuilder来操作字符串拼接工作
普通代码中使用加号连接字符串会被jvm优化成使用StringBuilder拼接(感兴趣的朋友可以试一下)
本博客为博主原创,转载请注明出处