前言
今天在刷leetcode的时候,发现直接使用String进行拼接与StringBuilder进行拼接的效率差得很多,于是摸索了一下他们底层实现的区别。
StringBuilder
构造函数
class StringBuilder extends AbstractStringBuilder
/**
* Constructs a string builder with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuilder() {
super(16);
}
class AbstractStringBuilder
/**
* The value is used for character storage.
*/
char[] value;
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
在调用无参构造方法的时候,StringBuilder默认会创建一个长度为16的字符数组。
append方法
public AbstractStringBuilder append(String str) {
// 为空判断
if (str == null)
return appendNull();
int len = str.length();.
// 判断是否需要扩容
ensureCapacityInternal(count + len);
// 加入到value数组中
str.getChars(0, len, value, count);
// 长度指针
count += len;
return this;
}
append方法会把每次加入的字符串暂存到value字符数组中。(如果传入的字符串为null,则存入字符串"null")当调用toString()方法时,就会把字符数组转换成一个String对象输出。
String
String的拼接分为两种情况
- 常量拼接
- 变量拼接
常量拼接
String str = "a" + "b" + "c";
以上这种情况被称为常量拼接,也就是每一个值都是确定好的,jvm在编译期就会直接完成拼接。
变量拼接
String a = "a";
String b = "b";
String c = "c";
String str = a + b + c + "d";
以上这种情况被称为变量拼接,只要在拼接过程中有一个变量,那么就属于变量拼接。由于String是不可变的,所以JVM会自动采用StringBuilder调用append方法把他们拼接起来。
验证
亲测使用IDEA或者使用jdgui工具反编译class文件的结果都是和上述的代码一模一样,也就是无法看出jvm的优化。在网上搜索了许久以后,发现有jad工具可以把jvm优化后的代码展示出来。
下载地址: https://varaneckas.com/jad/
使用方法:
- 下载并解压
- 添加到环境变量中
- 切换到class文件所在目录
- 利用指令 jad xxx.class
以下是自己做的一个小实验:
源代码
public static void main(String[] args) {
String str = "init";
for (int i = 0; i < 100000; i++) {
str += i;
}
}
jad工具反编译后的代码
public static void main(String args[]) {
String str = "init";
for(int i = 0; i < 0x186a0; i++)
str = (new StringBuilder()).append(str).append(i).toString();
}
总结
从测试可以看出String在进行拼接的时候jvm会根据常量拼接和变量拼接两种情况进行优化。
但值得注意的是,变量拼接的情况下,每次都会new一个新的StringBuilder对象来使用,使用完就会销毁。对象的创建和销毁的开销虽然平时看不出来,可是一旦拼接次数多了的话,这个开销也是十分大的。因此在频繁的字符串变量拼接操作中,尽量由自己显式创建StringBuilder而不要让jvm自己去优化。(避免了jvm预热、5000次拼接的情况下,使用StringBuilder只需要1ms,而直接使用字符串相加则需要64ms)