Java字符串連接不同方法的性能比較
1. 概述
在 Java 中,字符串連接是處理文本操作時的常見操作。但是,您選擇的連接字符串的方式可能會對應用程序的性能產生重大影響。了解可用的不同串聯方法及其性能特徵對於編寫高效且優化的代碼至關重要。
在本教程中,我們將深入研究 Java 中不同的字符串連接方法。我們將使用 JHM 工具對這些方法的執行時間進行基準測試和比較。
2. 基準測試
我們將採用 JMH(Java Microbenchmark Harness)進行基準測試。 JMH 提供了一個用於測量小代碼片段性能的框架,使開發人員能夠分析和比較不同的實現。
在繼續之前,讓我們設置環境來運行基準測試。核心處理器和註釋處理器都可以在 Maven Central 中找到。
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.36</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.36</version>
</dependency>
3. 不可變的字符串連接
不可變字符串串聯涉及為每個串聯操作創建一個新的不可變String
實例。每次發生連接時,都會生成一個新的字符串對象。此方法簡單明了,但由於創建多個對象,內存效率可能較低。
現在,讓我們快速瀏覽一下各種不可變方法:
3.1.使用加法 (+) 運算符
這是最簡單的方法,也可能是我們最熟悉的方法。它可以使用加法 + 運算符連接字符串文字、變量或兩者的組合:
String str1 = "String";
String str2 = "Concat";
String result = str1 + str2;
3.2.使用concat()
方法
concat()
方法由String
類提供,可用於將兩個字符串連接在一起:
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 Stream
API,它也可從 Java 8 開始使用。它提供了一種對對象集合執行操作的表達方式,並允許我們使用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.add("String");
buffer.add('Concat");
String result = buffer.toString();
4.2.使用StringBuilder
StringBuilder
用途與StringBuffer
相同。它們之間的唯一區別是**StringBuilder
不是線程安全的,而StringBuffer
是。它非常適合不關心線程安全的單線程場景:**
StringBuilder builder = new StringBuilder();
builder.add("String");
builder.add('Concat");
String result = builder.toString();
4.3.使用StringJoiner
StringJoiner
是從 Java 8 開始的新類。它的功能類似於StringBuilder
,提供了一種用分隔符連接多個字符串的方法。雖然它與StringBuilder
相同,但StringJoiner
不是線程安全的:
StringJoiner joiner = new StringJoiner("");
joiner.add("String");
joiner.add('Concat");
String result = joiner.toString();
5. 績效評估
在本節中,我們將評估不同字符串連接方法在各種場景下的性能,包括循環迭代和批處理。
5.1.循環迭代
我們將在循環內評估字符串連接性能,其中字符串被重複連接。在這種情況下,我們將評估具有不同迭代次數的不同方法的性能。
我們將使用不同的迭代(100、1000 和 10000)運行測試,以了解計算時間如何隨迭代次數變化。讓我們從不可變方法開始:
迭代次數 | |||
方法 | 100 | 1000 | 10000 |
+ Operator | 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 | 100 | 10000 |
StringBuffer | 1.326 | 13.080 | 128.746 |
StringBuilder | 0.512 | 4.599 | 43.306 |
StringJoiner | 0.569 | 5.873 | 59.713 |
從上圖中,我們可以觀察到這些類別之間的不同行為,即計算時間隨著迭代次數的增加而增加。
計算時間隨著可變類別中的數據大小線性增加。而在不可變類別中,計算時間呈指數增長。串聯運算增加十倍會導致計算時間增加一百倍。
我們還可以觀察到,除了String.format().
它的速度明顯慢,比同類其他方法花費的時間長幾倍。顯著的性能差異可歸因於String.format()
執行的額外解析和替換操作。
在不可變類別中, StringBuilder
是最快的選擇,因為與StringBuffer
相比,它缺乏同步開銷。另一方面, StringJoiner
性能比StringBuilder
稍慢,因為它每次連接時都需要附加分隔符。
5.2.批量處理
讓我們看一下一些允許在可變類別中一次性連接 2 個以上字符串的方法。下面的示例說明了具有 5 個串聯的單個String.join()
:
String result = String.join("", str1, str2, str3, str4, str5);
我們將在本節中了解這些方法的性能。與上一節類似,我們將批量運行不同數量的串聯(100、1000 和 10000)的測試:
串聯數 | |||
方法 | 100 | 1000 | 10000 |
+ Operator | 0.777 | 33.768 | 堆棧溢出錯誤 |
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()
再次排在最後。
六,結論
在本文中,我們探索了 Java 中的不同字符串連接方法,並使用 JMH 評估了它們的性能。
我們通過循環迭代中的性能評估觀察了可變類別和不可變類別之間的不同行為。可變類別的計算時間隨著數據大小線性增加,而不可變類別則呈指數增長。由於縮放模式,每當我們在循環中連接字符串時,我們都應該採用可變方法。
在我們總體介紹的所有方法中, StringBuilder
是最快的方法,而String.format()
由於其額外的解析和替換操作而成為最慢的方法。
與往常一樣,所有源代碼都可以在 GitHub 上獲取。