写点什么

Java 字符串拼接性能实测:基于 JMH 的微基准测试

  • 2025-05-08
    新加坡
  • 本文字数:2374 字

    阅读完需:约 8 分钟

Java 字符串拼接性能实测:基于 JMH 的微基准测试

Java 字符串拼接性能实测:基于 JMH 的微基准测试

在 Java 开发中,字符串拼接操作无处不在。你可能会直接使用 +,也可能选择 StringBuilderStringBuffer。它们在性能上究竟有何差别?在循环中拼接多个字符串时,哪种方式更高效?


本文基于 JMH(Java Microbenchmark Harness)进行了系统性测试,并使用 GitHub Actions 在 Ubuntu 环境中实测了不同字符串拼接方式的性能。



🧪 测试目标

比较以下三种拼接方式在高频场景下的性能差异:


  1. + 操作符(语法糖,编译期转为 StringBuilder.append

  2. StringBuilder.append

  3. StringBuffer.append



🧰 项目创建与配置(Maven)

1. 创建基础工程

mvn archetype:generate \  -DgroupId=com.xinchentechnote \  -DartifactId=string-jmh \  -DarchetypeArtifactId=maven-archetype-quickstart \  -DinteractiveMode=false
复制代码

2. 配置 pom.xml

<dependencies>  <dependency>    <groupId>org.openjdk.jmh</groupId>    <artifactId>jmh-core</artifactId>    <version>1.37</version>  </dependency>  <dependency>    <groupId>org.openjdk.jmh</groupId>    <artifactId>jmh-generator-annprocess</artifactId>    <version>1.37</version>    <scope>provided</scope>  </dependency></dependencies>
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>17</source> <target>17</target> <annotationProcessorPaths> <path> <groupId>org.openjdk.jmh</groupId> <artifactId>jmh-generator-annprocess</artifactId> <version>1.37</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins></build>
复制代码



📄 测试代码实现

package com.xinchentechnote;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})@OutputTimeUnit(TimeUnit.MICROSECONDS)@State(Scope.Thread)public class StringConcatBenchmark {
private String str1 = "Hello"; private String str2 = "World"; private String str3 = "Java"; private int count = 100;
@Benchmark public String testStringBuilder() { StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; i++) { sb.append(str1); sb.append(str2); sb.append(str3); } return sb.toString(); }
@Benchmark public String testStringBuffer() { StringBuffer sb = new StringBuffer(); for (int i = 0; i < count; i++) { sb.append(str1); sb.append(str2); sb.append(str3); } return sb.toString(); }
@Benchmark public String testStringPlus() { String result = ""; for (int i = 0; i < count; i++) { result += str1; result += str2; result += str3; } return result; }}
复制代码



🚀 GitHub Actions 自动化测试配置

name: JMH Benchmarks
on: workflow_dispatch:
jobs: benchmark: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-java@v3 with: distribution: temurin java-version: '17' cache: maven - run: mvn clean install -DskipTests - run: java -jar target/benchmarks.jar StringConcatBenchmark
复制代码



📊 实测结果(Ubuntu + GitHub Actions)

📈 吞吐量测试(Throughput)

单位:每微秒执行的操作数(ops/us)


⏱ 平均耗时测试(AverageTime)

单位:每次操作的平均耗时(us/op)




🔍 原理解析

  • StringBuilder:非线程安全但性能最好,推荐在循环中使用。

  • StringBuffer:线程安全但性能略低。

  • + 操作符:虽然直观,但在循环中极其低效,会频繁创建临时对象,带来 GC 压力。



✅ 使用建议



🏁 总结

本次 JMH 实测验证了开发经验中的最佳实践:在高频场景中,推荐使用 StringBuilder 进行字符串拼接。



💡 JMH 小知识:Java 微基准测试利器

JMH (Java Microbenchmark Harness) 是由 Oracle 和 OpenJDK 团队专为 Java 编写的微基准测试框架,用于衡量 Java 方法在纳秒到微秒级别的性能表现。JMH 特别适用于需要精细分析方法调用开销、编译优化、副作用等对性能影响的场景。

核心术语与注解解释:


推荐配置参数说明:

@BenchmarkMode({Mode.Throughput, Mode.AverageTime}) // 同时测吞吐量和平均耗时@OutputTimeUnit(TimeUnit.MICROSECONDS)              // 输出微秒为单位@State(Scope.Thread)                                // 每个线程独立状态@Fork(1)                                             // 启动 1 次 JVM@Warmup(iterations = 5, time = 1)                   // 预热 5 次,每次 1 秒@Measurement(iterations = 5, time = 1)              // 采样 5 次,每次 1 秒
复制代码

为什么不能简单用 System.currentTimeMillis?

因为 JVM 启动初期 JIT 编译未完成、内联尚未展开、逃逸分析等优化机制尚未介入,初始运行的耗时非常不稳定。而 JMH 通过:


  • 自动 warm-up 预热阶段;

  • 多轮 fork JVM 隔离优化影响;

  • 统计误差和波动范围(Error);


确保了测得结果更真实、更接近应用实际表现。




希望本文能为你在日常开发与性能优化中提供量化参考!


发布于: 刚刚阅读数: 3
用户头像

Talk is cheap, show me the code. 2019-04-17 加入

勿在浮沙筑高台,沉淀、记录个人的技术笔记与总结。现某金融科技公司开发人员,主要负责网络编程,涉及相关网关组件架构设计与重构,低时延性能优化等。

评论

发布
暂无评论
Java 字符串拼接性能实测:基于 JMH 的微基准测试_歆晨技术笔记_InfoQ写作社区