为什么90%的Java程序员都用错了字符串拼接?

上线前好好的代码,用户一多就卡成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垃圾回收根本忙不过来!

为什么90%的Java程序员都用错了字符串拼接?


数据来源: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%的Java程序员都用错了字符串拼接?


代码编辑器截图:优化前后的订单日志拼接对比

⚠️ 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锁),虽然慢一点,但安全!

记住这张图,再也不会用错!

为什么90%的Java程序员都用错了字符串拼接?

简单总结:

  • 单次拼接:用+(代码简洁,编译器优化)
  • 循环/频繁拼接:用StringBuilder(复用对象,指定容量)
  • 多线程拼接:用StringBuffer(线程安全,牺牲一点性能)

最后送你一个性能优化 Checklist

  1. 循环拼接字符串?先在循环外new StringBuilder
  2. 知道大致长度?指定初始容量(如new StringBuilder(1024))!
  3. 多线程环境?换StringBuffer,别用StringBuilder!
  4. 不确定用啥?看循环次数:10次以内用+,10次以上用StringBuilder!

快去看看你项目里的字符串拼接代码,说不定改完就能多抗10倍流量!

(图片来源:CSDN博客、博客园、Java开发社区)

© 版权声明

相关文章

9 条评论

您必须登录才能参与评论!
立即登录
  • 头像
    家琪老师 读者

    没看源码?没反过?

    无记录
  • 头像
    深刻 读者

    我不是Java程序员都知道用StringBuilder,除了简单拼接,谁没事用加号啊?还90%,是培训班出来的90%吧?

    无记录
  • 头像
    听风少女 投稿者

    实际项目中真的不少

    无记录
  • 头像
    杨杨 读者

    用户一次下十万单 好好想想吧

    无记录
  • 头像
    有头发的是绾绾 投稿者

    基础都没有敢上工?

    无记录
  • 头像
    阿朝朝朝啊 投稿者

    脱裤子放屁是java的看家本领

    无记录
  • 头像
    爱吃西瓜的欧阳长空 读者

    兄弟你用的是jdk6?jdk8之后string底层就是stringbuilder,你那是老黄历了

    无记录
  • 头像
    xsecret_61 投稿者

    Java程序员都卷到这个程度了吗

    无记录
  • 头像
    好想有只绒咩咩 投稿者

    那么问题来了,什么操作需要大量拼接字符串,超过上百行都没见过,脱离实际挖性能没意义。

    无记录