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获取。


原始标题:Performance Comparison Between Different Java String Concatenation Methods