Java中八種字符串拼接方式,性能很意外
環(huán)境:SpringBoot3.2.5
1. 簡介
在開發(fā)中,字符串拼接是非常常見的操作,廣泛應(yīng)用于日志記錄、數(shù)據(jù)處理、用戶界面生成等場景。然而,不同的字符串拼接方式在性能上有著顯著的差異,這一點往往被開發(fā)人員忽視。本文將詳細介紹 Java 中的 8 種字符串拼接方式,并通過性能測試揭示這些方法的實際表現(xiàn),結(jié)果令人意外。
Java中可以使用如下8種方式進行字符串的拼接:
- "+"操作符
- String#concat方法
- String#join方法
- String#format方法
- Stream流方式
- StringBuffer
- StringBuilder
- StringJoiner
下面我們依次介紹這8中方式;接下來的示例我們都將建立在JMH之上。
2. 實戰(zhàn)案例
2.1 "+"操作符
這是最簡單的方法,也是我們可能最熟悉的一種。它可以使用加號(+)運算符來連接字符串字面量、變量或者二者的組合:
@Benchmark
public void plusOperator(Blackhole hole) {
String str1 = "Pack";
String str2 = " xxxooo";
String result = str1 + str2;
hole.consume(result);
}
2.2 String#concat方法
concat() 方法由 String 類提供,可用于將兩個字符串連接在一起。
@Benchmark
public void concat(Blackhole hole) {
String str1 = "Pack";
String str2 = " xxxooo";
String result = str1.concat(str2);
hole.consume(result);
}
2.3 String#join方法
String.join() 是 Java 8 以后新增的靜態(tài)方法。它允許使用指定的分隔符連接多個字符串。
@Benchmark
public void join(Blackhole hole) {
String str1 = "Pack";
String str2 = " xxxooo";
String result = String.join("", str1, str2);
hole.consume(result);
}
2.4 String#format方法
String.format() 用于使用占位符和格式指定符格式化字符串。通過使用實際值替換占位符,可以創(chuàng)建格式化字符串。
@Benchmark
public void format(Blackhole hole) {
String str1 = "Pack";
String str2 = " xxxooo";
String result = String.format("%s%s", str1, str2);
hole.consume(result);
}
2.5 Stream流
它為在對象集合上執(zhí)行操作提供了一種富有表現(xiàn)力的方法,并允許我們使用 Collectors.joining() 來集中字符串。
@Benchmark
public void stream(Blackhole hole) {
List<String> strList = Arrays.asList("Pack", " xxxooo");
String result = strList.stream().collect(Collectors.joining());
hole.consume(result);
}
2.6 StringBuffer
StringBuffer
提供了一個可變的字符序列。它允許對字符串進行動態(tài)操作而無需創(chuàng)建新的對象。值得一提的是,它被設(shè)計為線程安全的,這意味著它可以被多個線程安全地并發(fā)訪問和修改。
@Benchmark
public void stringBuffer(Blackhole hole) {
StringBuffer buffer = new StringBuffer();
buffer.append("Pack") ;
buffer.append(" xxxooo") ;
String result = buffer.toString() ;
hole.consume(result) ;
}
2.7 StringBuilder
StringBuilder
和 StringBuffer
的用途相同。它們之間唯一的區(qū)別是 StringBuilder
不是線程安全的,而 StringBuffer
是。在不需要考慮線程安全的單線程場景中,StringBuilder
是非常完美的選擇。
@Benchmark
public void stringBuilder(Blackhole hole) {
StringBuilder builder = new StringBuilder() ;
builder.append("Pack") ;
builder.append(" xxxooo") ;
String result = builder.toString() ;
hole.consume(result) ;
}
2.8 StringJoiner
StringJoiner
是從 Java 8 開始引入的一個新類。它的功能與 StringBuilder
類似,提供了一種使用分隔符連接多個字符串的方式。盡管它與 StringBuilder
有相似之處,但 StringJoiner
也不是線程安全的。
@Benchmark
public void stringJoiner(Blackhole hole) {
StringJoiner joiner = new StringJoiner("");
joiner.add("Pack") ;
joiner.add(" xxxooo") ;
String result = joiner.toString() ;
hole.consume(result) ;
}
以上我們簡單的介紹了每一種字符串拼接的使用。
3. 性能測試
接下來,我們通過JMH進行性能的測試,首先我們在類上添加如下的注解:
// 預(yù)熱1s鐘,預(yù)熱3次
@Warmup(iterations = 3, time = 1)
// 啟動多少個進程
@Fork(value = 1, jvmArgsAppend = {"-Xms512m", "-Xmx512m"})
// 指定顯示結(jié)果(枚舉值)
@BenchmarkMode(Mode.AverageTime)
// 指定顯示結(jié)果單位(枚舉值)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
// 迭代10次,每次2s
@Measurement(iterations = 10, time = 2, timeUnit = TimeUnit.SECONDS)
public class StringJoinTest {
// 上面8種字符串拼接的方法
}
在本示例中,我們將通過main的方式運行,這種方式稍微有點不是特別準確,你可以選擇jar的方式。
public static void main(String[] args) throws Exception {
Options options = new OptionsBuilder()
// 你要測試的類
.include(StringJoinTest.class.getSimpleName())
// 啟動幾個進程
.forks(1).build();
new Runner(options).run();
}
最終測試結(jié)果如下:
圖片
下面是對上面每一列的說明(以第一行concat測試為例說明)
Benchmark
a.說明:基準測試的名稱,通常是類名+方法名。
b.示例:StringJoinTest.concat。
Mode
a.說明:基準測試的模式,常見模式有:
- avgt:平均時間模式(Average Time),每個操作的平均時間。
- thrpt:吞吐量模式(Throughput),單位時間內(nèi)完成的操作次數(shù)。
- sample:采樣模式,用于收集詳細的統(tǒng)計信息。
- ss:穩(wěn)定狀態(tài)模式,用于評估長時間運行的性能穩(wěn)定性。
b.示例:avgt
Cnt
- 說明:迭代次數(shù),即基準測試運行的次數(shù)。
- 示例:10
Score
a.說明:基準測試的主要結(jié)果指標。根據(jù)模式的不同,這個值的含義也不同。
- avgt:每個操作的平均時間(秒、毫秒、納秒等)。
- thrpt:每秒完成的操作次數(shù)(ops/s)。
b.示例:8.584
Error
- 說明:結(jié)果的標準誤差(Standard Error),表示結(jié)果的不確定性。
- 示例:0.175
Units
- 說明:結(jié)果的單位
- 示例:ns/op(每操作納秒)
性能排序
- (+)plusOperator:6.154 ± 0.119 ns/op
- concat:8.584 ± 0.175 ns/op
- stringBuilder:11.560 ± 0.216 ns/op
- stringBuffer:12.340 ± 0.150 ns/op
- stringJoiner:29.932 ± 0.236 ns/op
- join:28.210 ± 0.241 ns/op
- stream:34.293 ± 0.284 ns/op
- format:409.691 ± 2.941 ns/op