京东一面:为什么 IDEA 建议去掉 StringBuilder,而要使用 “+” 拼接字符串?
今天咱们来聊聊一个很常见的开发场景:字符串拼接
。在日常开发中,字符串拼接几乎是每个 Java 开发者都会用到的操作。但最近,有朋友在面试时被问到一个问题:“为什么 IDEA 建议用‘+’拼接字符串,而不是用 StringBuilder?”这问题听起来是不是有点反直觉?毕竟,在大家的普遍认知中,用 StringBuilder 拼接字符串效率更高。
一、“+” 拼接字符串
先来说说“+”拼接字符串。在 Java 中,“+” 是一个非常直观的字符串拼接操作符。比如,"Hello" + " " + "World"
,结果就是 "Hello World"
。简单、直接、易读,这是它的优点。
但长期以来,我们一直被告知:“+”拼接字符串效率很低,尤其是在循环中。因为每次拼接都会创建一个新的字符串对象,导致大量的临时对象产生,增加了垃圾回收的负担。所以,很多开发者会习惯性地使用 StringBuilder 来代替“+”,尤其是在处理复杂的字符串拼接时。
然而,从 JDK 5 开始,Java 编译器做了一个优化——当你使用“+”拼接字符串时,编译器会自动将其优化为使用 StringBuilder 的方式。也就是说,"a" + "b"
在编译后,实际上会被编译器转换为 new StringBuilder().append("a").append("b").toString()
。这样一来,“+”拼接字符串的性能问题就得到了解决。
二、实践验证
为了验证这一点,我们来做一个简单的实验。写一个测试类,分别用“+”和 StringBuilder 拼接字符串,然后比较它们的性能。
public String concatenationStringByPlus(String prefix, int i) {
return prefix + "-" + i;
}
public String concatenationStringByStringBuilder(String prefix, int i) {
return new StringBuilder().append(prefix).append("-").append(i).toString();
}
然后,我们用 JUnit 测试用例分别调用这两种方法,拼接 100000 次,看看耗时情况:
@Test
public void testStringConcatenationByPlus() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
concatenationStringByPlus("testByPlus:", i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用 '+' 拼接 100000 次,耗时:" + (endTime - startTime) + " 毫秒");
}
@Test
public void testStringConcatenationByStringBuilder() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
concatenationStringByStringBuilder("testByStringBuilder:", i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用 StringBuilder 拼接 100000 次,耗时:" + (endTime - startTime) + " 毫秒");
}
运行结果:
使用 '+' 拼接 100000 次,耗时:33 毫秒
使用 StringBuilder 拼接 100000 次,耗时:36 毫秒
可以看到,两者的耗时几乎一致。这说明在普通拼接场景下,“+” 和 StringBuilder 的性能几乎没有区别。而且,“+” 的代码更简洁、更易读
。
三、循环拼接的“陷阱”
那么,是不是在所有场景下,“+” 都和 StringBuilder 一样高效呢?答案是否定的。当涉及到循环拼接时,“+” 的效率问题就暴露出来了。
我们再做一个实验,模拟循环拼接一个长字符串。这次,我们分别用“+”和 StringBuilder 来拼接 10000 次:
@Test
public void testLoopConcatenationByPlus() {
long startTime = System.currentTimeMillis();
String str = "Initial String";
for (int i = 0; i < 10000; i++) {
str = str + "-" + i;
}
long endTime = System.currentTimeMillis();
System.out.println("使用 '+' 循环拼接 10000 次,耗时:" + (endTime - startTime) + " 毫秒");
}
@Test
public void testLoopConcatenationByStringBuilder() {
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder("Initial String");
for (int i = 0; i < 10000; i++) {
sb.append("-").append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("使用 StringBuilder 循环拼接 10000 次,耗时:" + (endTime - startTime) + " 毫秒");
}
运行结果:
使用 '+' 循环拼接 10000 次,耗时:463 毫秒
使用 StringBuilder 循环拼接 10000 次,耗时:13 毫秒
可以看到,循环拼接时,“+” 的效率远远低于 StringBuilder。这是因为每次循环时,“+” 都会创建一个新的 StringBuilder 对象,而 StringBuilder 只需要在同一个对象上追加内容,效率自然更高
。
四、为什么 IDEA 建议用“+”?
既然在循环拼接中 StringBuilder 更高效,为什么 IDEA 还要建议用“+”呢?原因在于编译器的优化和代码的可读性
。
对于简单的字符串拼接,编译器会自动将“+”优化为 StringBuilder 的形式。在这种情况下,使用“+”不仅代码更简洁,而且性能也一样好。而 StringBuilder 的代码相对冗长,可读性稍差
。
但如果是在循环中拼接字符串,IDEA 通常会提示你使用 StringBuilder,因为它能明显提升性能
。
五、总结
通过以上实验和分析,我们可以得出以下结论:
- 普通拼接场景:使用“+”和使用 StringBuilder 的性能几乎一致。由于“+”的代码更简洁、更易读,推荐在普通拼接场景下使用“+”。
- 循环拼接场景:推荐使用 StringBuilder。因为“+”在循环中会不断创建新的 StringBuilder 对象,导致性能大幅下降,而 StringBuilder 只需要初始化一次,效率更高。
围观朋友⭕:dabinjava
#java#