017:为什么不建议在循环中使用“+”拼接字符串
典型答案
由于字符串对象是不可变的,所以每次循环都会对操作符左右两边的字符串进行拷贝,并生成一个新的字符串对象。如果循环n次,则这个过程需要n的平方级的时间;并且在这个过程中还创建了很多短命的中间对象。
如果要使用循环构建一个大的字符串,推荐使用StringBuilder代替String,使用StringBuilder的append()方法进行字符串连接,并在循环结束后将StringBuilder对象转为String对象。StringBuilder的原理是预先分配了一个足够大小的缓冲区,然后循环的过程就是往缓冲区里填充数据,比使用“+”做字符串连接的效率要高很多。
知识点梳理
上面的答案是理论知识,这里看下实际案例,假设有如下代码,循环10000次将随机长度80的字符串连接为一个大的字符串,使用“+”和使用StringBuilder的方法之间的差距是两个数量级(我的环境:Mac OS 16G + JDK 1.8)
public class StringConcatExample {
public static void main(String[] args) {
long s1 = System.currentTimeMillis();
new StringConcatExample().implicit();
System.out.println("使用"+"拼接:" + (System.currentTimeMillis() - s1));
s1 = System.currentTimeMillis();
new StringConcatExample().exlicit();
System.out.println("使用"StringBuilder"拼接:" + (System.currentTimeMillis() - s1));
}
public String implicit() {
String result = "";
for (int i = 0; i < 10000; i++) {
result += (i + ",");
}
return result;
}
public String exlicit() {
StringBuilder result = new StringBuilder();
for (int i = 0; i < 10000; i++) {
result.append(i).append(",");
}
return result.toString();
}
}
上述代码的运行结果如下:
WechatIMG22.jpeg
使用javac StringConcatExample.java
命令编译源文件,使用javap -c StringConcatExample
命令查看字节码文件的内容。
implicit()方法的字节码:
public java.lang.String implicit();
Code:
0: ldc #16 // String
2: astore_1
3: iconst_0
4: istore_2
5: iload_2
6: sipush 10000
9: if_icmpge 42
12: new #7 // class java/lang/StringBuilder
15: dup
16: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
19: aload_1
20: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
23: iload_2
24: invokevirtual #17 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
27: ldc #18 // String ,
29: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
35: astore_1
36: iinc 2, 1
39: goto 5
42: aload_1
43: areturn
可以看出,第9行到第39行构成了一个循环体:在第9行的时候做条件判断,如果不满足循环条件,则跳转到42行。编译器做了一定程度的优化,在12行new了一个StringBuilder对象,然后再20行、24行、29进行了三次append方法的调用,不过重点是,每次循环都会new一个StringBuilder对象。
再看explicit()方法的字节码,如下所示:
public java.lang.String exlicit();
Code:
0: new #7 // class java/lang/StringBuilder
3: dup
4: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
7: astore_1
8: iconst_0
9: istore_2
10: iload_2
11: sipush 10000
14: if_icmpge 34
17: aload_1
18: iload_2
19: invokevirtual #17 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
22: ldc #18 // String ,
24: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: pop
28: iinc 2, 1
31: goto 10
34: aload_1
35: invokevirtual #12 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
38: areturn
14行到31行构成了循环体,可以看出,在第4行(循环体外)就构建好了StringBuilder对象,然后再循环体内只进行append()方法的调用。这就从字节码层面解释了为什么不建议在循环体内使用“+”执行字符串的拼接。
参考资料
- 《Effective Java(第二版)》
- 《Java编程思想》
- 在Linux系统运行WinForm程序
- 将ZIP文件添加到程序集资源文件然后在运行时解压文件
- Android中App安装位置详解
- Java面试题系列之基础部分(二)——每天学5个问题
- Java面试题系列之基础部分(四)——每天学5个问题
- 使用ORM框架,必须迁就数据库的设计吗?
- 使用OQL+SQLMAP解决ORM多表复杂的查询问题
- PostgreSQL的.NET驱动程序Npgsql中参数对象的一个Bug
- 和Emoji相关的那些开源项目
- PostgreSQL的PDF.NET驱动程序构建过程
- 基于Docker的PHP开发环境
- 以太坊·物流场景初探
- Python接口自动化-3-POST请求
- 【Python环境】Python中的结构化数据分析利器-Pandas简介
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Shell脚本加密经验分享
- python提示每天债券打新
- Python3下基于Scapy库完成网卡抓包解析
- pandas:解决groupby().apply()方法打印两次
- pandas:字段值插入数据表第一行的解决办法
- Recommending items to more than a billion people(面向十亿级用户的推荐系统)
- pandas:对字符串类型做差分比较
- pandas:由列层次化索引延伸的一些思考
- 远程过程调用 Java RMI 技术 远程控制
- Real-time Xenomai 3 example 1
- Electron 常见问题收录II
- SQL性能调优技巧
- Linux查看CUDA版本以及cudnn版本号
- 数据结构算法操作试题(C++/Python)——最长有效括号
- 数据结构算法操作试题(C++/Python)——两两交换链表中的节点