1. 概述
在Java中,字符串拼接是文本处理中的常见操作。但不同的拼接方式对应用性能的影响差异巨大。理解各种拼接方法的性能特征,是编写高效代码的关键。
本教程将深入探讨Java中的多种字符串拼接方法,并使用JMH工具进行基准测试和性能对比。
2. 基准测试
我们将采用JMH (Java Microbenchmark Harness)进行基准测试。JMH为测量小型代码片段性能提供了专业框架,帮助开发者分析不同实现的性能差异。
在开始前,先配置基准测试环境。核心库和注解处理器都可以在Maven中央仓库找到。
3.2. 使用concat()方法
String类提供的concat()方法可用于拼接两个字符串:
String str1 = "String";
String str2 = "Concat";
String result = str1.concat(str2);
3.3. 使用String.join()方法
String.join()是Java 8新增的静态方法,允许使用指定分隔符拼接多个字符串:
String str1 = "String";
String str2 = "Concat";
String result = String.join("", str1, str2);
3.4. 使用String.format()方法
String.format()通过占位符和格式说明符格式化字符串,能创建格式化输出:
String str1 = "String";
String str2 = "Concat";
String result = String.format("%s%s", str1, str2);
3.5. 使用Java Stream API
最后看Java 8引入的Stream API,它通过Collectors.joining()提供了一种表达式的字符串拼接方式:
List<String> strList = List.of("String", "Concat");
String result = strList.stream().collect(Collectors.joining());
4. 可变字符串拼接
现在转向可变字符串拼接。这种方式使用可变字符序列,底层对象可直接修改以追加或插入字符。可变拼接效率高,无需每次操作都创建新对象。
可变方法主要有以下几种:
4.1. 使用StringBuffer
StringBuffer提供可变字符序列,支持动态字符串操作而不创建新对象。关键特点是线程安全,可被多线程并发访问和修改:
StringBuffer buffer = new StringBuffer();
buffer.append("String");
buffer.append("Concat");
String result = buffer.toString();
4.2. 使用StringBuilder
StringBuilder与StringBuffer功能相同,唯一区别是StringBuilder非线程安全。非常适合单线程场景:
StringBuilder builder = new StringBuilder();
builder.append("String");
builder.append("Concat");
String result = builder.toString();
4.3. 使用StringJoiner
StringJoiner是Java 8新增类,功能类似StringBuilder,支持用分隔符拼接字符串。同样是非线程安全的:
StringJoiner joiner = new StringJoiner("");
joiner.add("String");
joiner.add("Concat");
String result = joiner.toString();
5. 性能评估
本节将在循环迭代和批处理场景下评估不同拼接方法的性能。
5.1. 循环迭代
评估循环中重复拼接字符串的性能,测试不同迭代次数下的表现。测试迭代次数为100、1000和10000次。
先看不可变方法的表现:
迭代次数 | 方法 | 100 | 1000 | 10000 |
---|---|---|---|---|
+运算符 | 3.369 | 322.492 | 31274.622 | |
concat() | 3.479 | 332.964 | 32526.987 | |
String.join() | 4.809 | 331.807 | 31090.466 | |
String.format() | 19.831 | 1368.867 | 121656.634 | |
Stream API | 10.253 | 379.570 | 30803.985 |
再看可变方法的表现:
迭代次数 | 方法 | 100 | 1000 | 10000 |
---|---|---|---|---|
StringBuffer | 1.326 | 13.080 | 128.746 | |
StringBuilder | 0.512 | 4.599 | 43.306 | |
StringJoiner | 0.569 | 5.873 | 59.713 |
从数据可明显看出两类方法的性能差异:
✅ 可变方法:计算时间随数据量线性增长
❌ 不可变方法:计算时间呈指数级增长,操作量增加10倍,耗时增加约100倍
⚠️ String.format()明显慢于其他方法,因其需要额外的解析和替换操作。
在可变方法中,StringBuilder最快(无同步开销),StringJoiner稍慢(需处理分隔符)。
5.2. 批处理
测试一次性拼接多个字符串的方法,例如单次String.join()拼接5个字符串:
String result = String.join("", str1, str2, str3, str4, str5);
不同拼接次数(100/1000/10000)的性能表现:
拼接次数 | 方法 | 100 | 1000 | 10000 |
---|---|---|---|---|
+运算符 | 0.777 | 33.768 | StackOverflowError | |
String.join() | 0.820 | 8.410 | 88.888 | |
String.format() | 3.871 | 38.659 | 381.652 | |
Stream API | 2.019 | 18.537 | 193.709 |
批处理场景下,计算时间仍呈线性增长。String.format()因解析开销再次垫底。
⚠️ +运算符在10000次拼接时直接栈溢出,这是典型的内存踩坑。
6. 结论
本文通过JMH评估了Java中不同字符串拼接方法的性能。
循环迭代测试中,可变方法时间复杂度为O(n),不可变方法为O(n²)。因此在循环中拼接字符串时,务必使用可变方法。
所有方法中:
✅ StringBuilder 性能最佳(无同步开销)
❌ String.format() 性能最差(解析开销大)
完整源代码可在GitHub获取。