Java字符串拼接那些事

时间:2022-05-17 11:57:15

关于Java字符串拼接,你一般用哪种方法?+还是StringBuilder?

使用+来拼接字符串,使用javap命令来反编译代码,可以看出实际上编译器会自动创建StringBuilder,调用它的append方法来拼接字符串。

如果在一个for循环语句中,循环100次,使用+来拼接字符串的话,就会创建100次StringBuilder对象,这样就很消耗内存,所以,在进行复杂字符串拼接的时候,还是建议使用StringBuilder来拼接字符串,就算循环1000次,也只是创建一次StringBuilder对象。


+ 操作和 StringBuilder 都能连接字符串,
+ 的优势在于:语法简单,容易书写。
缺点是:对于动态生成且合并次数过多的字符串,优化不足,需要反复申请内存。

StringBuilder 的优势在于:能够预先分配内存,对于需要进行多次拼接的字符串,优化了拼接时的内存和时间的消耗。缺点是:书写复杂,对于较简单且确定的字符串,运行效率反而比 + 差。

你可以这么理解:绝大多数情况下,如果字符串拼接在循环中(也就是需要反复进行),则偏向于使用StringBuilder ,而只是常规的拼接,则使用 + 即可。另外,如果需要在多个方法间传递字符串进行组装,则传递 StringBuilder 比较合适。


+ 与 StringBuilder

 

String s = “hello ” + user.getName();

这一句经过javac编译后的效果,的确等价于使用StringBuilder,但没有设定长度。

 

String s = new StringBuilder().append(“hello”).append(user.getName());

但是,如果像下面这样:

 

String s = “hello ”;
// 隔了其他一些语句
s = s + user.getName();

每一条语句,都会生成一个新的StringBuilder,这里就有了两个StringBuilder,性能就完全不一样了。如果是在循环体里s+=i; 就更加多得没谱。

据R大说,努力的JVM工程师们在运行优化阶段, 根据+XX:+OptimizeStringConcat(JDK7u40后默认打开),把相邻的(中间没隔着控制语句) StringBuilder合成一个,也会努力的猜长度。

所以,保险起见还是继续自己用StringBuilder并设定长度好了。

 

StringBuffer 与 StringBuilder

StringBuffer与StringBuilder都是继承于AbstractStringBuilder,唯一的区别就是StringBuffer的函数上都有synchronized关键字。

那些说StringBuffer “安全”的同学,其实你几时看过几个线程轮流append一个StringBuffer的情况???


使用+拼接和使用StringBuilder比较

 @Test
public void test() {
String str = "";
for (int i = 0; i < 10000; i++) {
str += "asjdkla";
}
}

上面这段代码经过优化后相当于:

 @Test
public void test() {
String str = null;
for (int i = 0; i < 10000; i++) {
str = new StringBuilder().append(str).append("asjdkla").toString();
}
}

一眼就能看出创建了太多的StringBuilder对象,而且在每次循环过后str越来越大,导致每次申请的内存空间越来越大,并且当str长度大于16时,每次都要扩容两次!而实际上toString方法在创建String对象时,调用了Arrays.copyOfRange方法来复制数据,此时相当于每执行一次,扩容了两次,复制了3次数据,这样的代价是相当高的。

 public void test() {
StringBuilder sb = new StringBuilder("asjdkla".length() * 10000);
for (int i = 0; i < 10000; i++) {
sb.append("asjdkla");
}
String str = sb.toString();
}

这段代码的执行时间在我的机器上都是0ms(小于1ms)和1ms,而上面那段代码则大约在380ms!效率的差距相当明显。

同样是上面的代码,将循环次数调整为1000000时,在我的机器上,有指定capacity时耗时大约20ms,没有指定capacity时耗时大约29ms,这个差距虽然和直接使用+操作符有了很大的提升(且循环次数增大了100倍),但是它依旧会触发多次扩容和复制。

将上面的代码改成使用StringBuffer,在我的机器上,耗时大约为33ms,这是因为StringBuffer在大部分方法上都加上了synchronized关键字来保证线程安全,执行效率有一定程度上的降低。

结论

现在根据上面的分析和测试可以知道:

  1. Java中字符串拼接不要直接使用+拼接。

  2. 使用StringBuilder或者StringBuffer时,尽可能准确地估算capacity,并在构造时指定,避免内存浪费和频繁的扩容及复制。

  3. 在没有线程安全问题时使用StringBuilder, 否则使用StringBuffer

  4. 两个字符串拼接直接调用String.concat性能最好。