上线前好好的代码,用户一多就卡成PPT?
前几天朋友公司出了个大事故——电商平台促销活动刚上线,订单系统突然卡死,CPU占用率飙升到100%!排查了3小时才发现,罪魁祸首竟然是一行看似无害的字符串拼接代码:
// 就是这行循环里的"+=",让系统直接崩了!
String log = "";
for (Order order : orderList) {
log += "用户" + order.getUserId() + "下单" + order.getAmount() + "元;";
}
你是不是也这么写过?觉得”+”号简单直观,性能差不到哪去?但数据不会说谎——10万次循环拼接,用”+”要18秒,用StringBuilder只要5毫秒!(相当于博尔特和我跑100米的差距)
3组实测数据,颠覆你的认知!
我们用JDK17做了个实验:在循环里拼接字符串10万次,看看不同方式的耗时——
|
拼接方式 |
耗时(毫秒) |
内存占用 |
适用场景 |
|
+ 运算符 |
18301 |
极高 |
单次简单拼接 |
|
StringBuilder |
5 |
低 |
循环/频繁拼接 |
|
StringBuffer |
6 |
中 |
多线程环境 |
差距3600倍! 为什么”+”这么慢?由于String是不可变的——每次”+=”都会创建新对象,循环10万次就会产生10万个临时对象,JVM垃圾回收根本忙不过来!

数据来源:CSDN博客《Java字符串拼接性能深度剖析》
什么时候用”+”?什么时候用StringBuilder?
别再一刀切啦!JDK5+早就偷偷优化了”+”——单次拼接时,编译器会自动把”+”转成StringBuilder,所以这两种写法性能几乎一样:
// 编译后完全一样!
String info1 = "用户" + name + "年龄" + age;
String info2 = new StringBuilder().append("用户").append(name).append("年龄").append(age).toString();
但循环里千万别用”+”! 编译器只会在单次拼接时优化,循环中每次”+=”都会新建StringBuilder,相当于:
// 循环里用"+",编译器会帮你写成这样(低效!)
String log = "";
for (int i=0; i<100000; i++) {
// 每次循环都new一个StringBuilder!
log = new StringBuilder(log).append("拼接内容").toString();
}
✅ 正确做法:循环外创建一个StringBuilder,全程复用:
StringBuilder log = new StringBuilder(); // 循环外创建
for (int i=0; i<100000; i++) {
log.append("拼接内容"); // 直接append,不新建对象
}
String result = log.toString(); // 循环结束再转成String
实战案例:从”卡成PPT”到”秒级响应”
朋友公司的订单日志优化前,用”+”拼接10万条订单数据要18秒,优化后用StringBuilder只要0.005秒,还顺便解决了内存溢出问题!
❶ 低效写法(千万别学!)
String log = "订单列表:";
for (Order order : orderList) {
// 每次循环创建新String,内存疯狂飙升!
log += "[" + order.getId() + ":" + order.getAmount() + "元],";
}
❷ 高效写法(推荐!)
// 预估长度1024字符,避免扩容(关键优化!)
StringBuilder log = new StringBuilder(1024);
log.append("订单列表:");
for (Order order : orderList) {
// 链式调用,一行搞定,全程不创建临时对象
log.append("[").append(order.getId()).append(":").append(order.getAmount()).append("元],");
}
// 最后转成String,只创建1个对象
String result = log.toString();

代码编辑器截图:优化前后的订单日志拼接对比
⚠️ 90%的人都踩过这3个坑!
坑1:以为”+”永远不如StringBuilder
真相:单次拼接时,”+”和StringBuilder性能几乎一样!列如name + age + “岁”,编译器会自动优化成new StringBuilder().append(name).append(age).append(“岁”),代码还更简洁~
坑2:忽视初始容量,导致频繁扩容
StringBuilder默认容量16,不够时会扩容为原容量×2+2(列如16→34→70…),每次扩容都要复制字符数组!正确做法:预估长度,列如拼接100个用户ID,直接new StringBuilder(1000)(每个ID约10字符)。
坑3:多线程用StringBuilder
危险! StringBuilder是非线程安全的,多线程并发append可能导致字符错乱。这时候应该用StringBuffer(方法加了synchronized锁),虽然慢一点,但安全!
记住这张图,再也不会用错!

简单总结:
- 单次拼接:用+(代码简洁,编译器优化)
- 循环/频繁拼接:用StringBuilder(复用对象,指定容量)
- 多线程拼接:用StringBuffer(线程安全,牺牲一点性能)
最后送你一个性能优化 Checklist
- 循环拼接字符串?先在循环外new StringBuilder!
- 知道大致长度?指定初始容量(如new StringBuilder(1024))!
- 多线程环境?换StringBuffer,别用StringBuilder!
- 不确定用啥?看循环次数:10次以内用+,10次以上用StringBuilder!
快去看看你项目里的字符串拼接代码,说不定改完就能多抗10倍流量!
(图片来源:CSDN博客、博客园、Java开发社区)



没看源码?没反过?
我不是Java程序员都知道用StringBuilder,除了简单拼接,谁没事用加号啊?还90%,是培训班出来的90%吧?
实际项目中真的不少
用户一次下十万单 好好想想吧
基础都没有敢上工?
脱裤子放屁是java的看家本领
兄弟你用的是jdk6?jdk8之后string底层就是stringbuilder,你那是老黄历了
Java程序员都卷到这个程度了吗
那么问题来了,什么操作需要大量拼接字符串,超过上百行都没见过,脱离实际挖性能没意义。