循环内拼接10万次数字,string耗时600ms以上,stringbuilder不到5ms;因string每次+=都新建对象触发gc,stringbuilder复用内部数组且无锁,性能差距达百倍级。

直接用循环拼接 10 万次数字,就能明显看出差距:String 耗时约 600ms 以上,StringBuilder 通常不到 5ms。
用 System.currentTimeMillis() 快速验证
这是最直观、无需额外依赖的测试方式。关键点是避免编译器优化干扰(比如把字符串字面量提前计算),所以用变量参与拼接:
- String 测试:每次用 str = str + i,实际每轮都新建对象
- StringBuilder 测试:复用同一个实例,调用 append(i)
- 务必在测试前后调用 System.currentTimeMillis(),取差值
- 建议运行多次取平均值,单次结果可能受 JIT 预热或 GC 干扰
用 JMH 做专业基准测试
JMH(Java Microbenchmark Harness)是 Oracle 官方推荐的微基准测试工具,能规避常见陷阱(如死码消除、常量折叠、JIT 预热不足):
- 标注 @Benchmark 方法,设置 @Warmup(预热轮次)和 @Measurement(正式测量轮次)
- 用 Blackhole.consume() 防止 JVM 优化掉无用计算
- 指定 @Fork 和 @State(Scope.Thread) 控制并发与实例生命周期
- 输出单位设为纳秒级(@OutputTimeUnit(TimeUnit.NANOSECONDS)),便于横向对比
注意控制变量,才能比得准
性能差异主要来自对象创建和同步开销,不是内存分配本身:
- StringBuffer 和 StringBuilder 扩容逻辑完全一致(旧容量 × 2 + 2),性能差距始终源于 synchronized 锁
- 不要在测试中混用 toString() 或打印语句,它们会引入 I/O 开销,污染结果
- 测试数据量要足够大(如 10⁴–10⁵ 次),小数据下差异不明显,JVM 优化可能掩盖问题
- Java 9+ 的 String 底层改用 byte[],内存更省,但不可变性没变——循环拼接仍产生大量临时对象
看懂结果背后的原理
数字只是表象,理解为什么慢才不会误用:
- String 慢:每次 + 都 new String,触发频繁 GC;即使编译器对静态拼接优化(转成 StringBuilder),运行时循环无法优化
- StringBuilder 快:内部复用 char[]/byte[],只在必要时扩容,无锁开销,执行路径最短
- StringBuffer 慢于 StringBuilder:方法全加 synchronized,单线程下锁获取/释放/内存屏障带来约 2.3 倍开销(实测 10 万次:12ms vs 28ms)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











