計算 Java 字串中某個序列出現的次數
1.概述
在 Java 中處理文字時,一個常見的需求是確定特定字元序列在String
中出現的次數。無論我們是分析日誌、清理文字數據,還是簡單地驗證內容,計算子字串的出現次數都是一項經常出現的任務。
在本教程中,我們將探索解決該問題的不同方法。
2.問題介紹
像往常一樣,讓我們透過一個例子來理解這個問題。假設我們有這個String
:
private final static String INPUT =
"This is a test string. This test is for testing the count of a sequence in a string. This string has three sentences.";
我們的目標是計算給定序列在此輸入String.
例如,如果我們計算“string”,
結果應該是 3。但是,如果給定序列是“string.”
(帶句點) ,
我們期望看到的結果應該是 2。
為了簡單起見,我們將跳過輸入驗證,例如檢查輸入String
或給定序列是否為null
,等等。
接下來,讓我們深入研究一下實作。
3. 在循環中使用indexOf()
最直接的方法是使用內建的String.indexOf()
方法。此方法傳回子字串首次出現的索引。
透過使用移動的起始位置重複呼叫它,我們可以計算所有出現的次數:
int countSeqByIndexOf(String input, String seq) {
int count = 0;
int index = input.indexOf(seq);
while (index != -1) {
count++;
index = input.indexOf(seq, index + seq.length());
}
return count;
}
接下來,讓我們透過單元測試來驗證這一點:
assertEquals(3, countSeqByIndexOf(INPUT, "string"));
assertEquals(2, countSeqByIndexOf(INPUT, "string."));
我們可以看到,這種方法是高效且易於理解。
4. 使用正規表示式與Matcher.find()
對於需要更多靈活性的情況,正規表示式是絕佳的選擇。 Java 的Pattern
和Matcher
類別允許我們掃描輸入並逐一找到匹配項。
接下來,讓我們使用Matcher.find()
方法來建立一個解決方案:
int countSeqByRegexFind(String input, String seq) {
// Alternative: Pattern pattern = Pattern.compile(seq, Pattern.LITERAL);
Matcher matcher = Pattern.compile(Pattern.quote(seq)).matcher(input)
int count = 0;
while (matcher.find()) {
count++;
}
return count;
}
需要注意的是, Pattern.quote(seq)
確保搜尋序列中任何特殊的正規表示式字元都按字面意思處理。換句話說,正規表示式中的任何字元都沒有特殊意義。例如,「 string.
」的字面意思是“string”
後面跟著一個句點,而不是“string”
後面跟著任何單一字元。
或者,我們也可以透過使用帶有LITERAL
標誌的Pattern.compile()
方法來實現。我們很快就會在另一個範例中看到這種方法。
接下來,我們來驗證這個解決方案是否能如預期運作:
assertEquals(3, countSeqByRegexFind(INPUT, "string"));
assertEquals(2, countSeqByRegexFind(INPUT, "string."));
如果我們執行這個測試,它就通過了。因此,我們的解決方案成功了。
5. 使用正規表示式和split()
另一種基於正規表示式的方法是使用split()
方法,將輸入String
按照我們想要計數的序列分開。拆分後的部分數量減一,就得到了計數結果:
int countSeqByRegexSplit(String input, String seq) {
Pattern pattern = Pattern.compile(seq, Pattern.LITERAL);
return pattern.split(input, -1).length - 1;
}
我們可以看到,這次我們使用了帶有LITERAL
標誌的Pattern.compile()
來停用正規表示式中字元的特殊意義。
接下來,我們來測試一下這種方法:
assertEquals(3, countSeqByRegexSplit(INPUT, "string"));
assertEquals(2, countSeqByRegexSplit(INPUT, "string."));
雖然這種方法很簡潔,但它可能不如Matcher.find()
方法直覺。儘管如此,它仍然展示了 Java 中正規表示式的多功能性。
6. 使用 Streams 和Matcher.results()
在 Java 9 或更高版本中,我們可以使用Matcher.results()
方法,該方法會產生匹配結果流。這使我們能夠利用 Java Streams
的強大功能來優雅地計算匹配次數。讓我們來看看具體實現:
int countSeqByStream(String input, String seq) {
long count = Pattern.compile(Pattern.quote(seq))
.matcher(input)
.results()
.count();
return Math.toIntExact(count);
}
接下來我們先透過測試來驗證一下:
assertEquals(3, countSeqByStream(INPUT, "string"));
assertEquals(2, countSeqByStream(INPUT, "string."));
我們可以看到,這種方法簡潔、實用,並且與現代 Java API 完美整合。
7. 使用 Apache Commons Lang 的StringUtils
最後,如果我們的專案已經依賴 Apache Commons Lang,我們可以直接使用StringUtils.countMatches()
實用程式方法來避免重複造輪子。下面這行程式碼提供了一個簡單的解決方案:
assertEquals(3, StringUtils.countMatches(INPUT, "string"));
assertEquals(2, StringUtils.countMatches(INPUT, "string."));
值得一提的是, countMatches()
內部使用indexOf()
方法:
public static int countMatches(final CharSequence str, final CharSequence sub) {
if (isEmpty(str) || isEmpty(sub)) {
return 0;
}
int count = 0;
int idx = 0;
while ((idx = CharSequenceUtils.indexOf(str, sub, idx)) != INDEX_NOT_FOUND) {
count++;
idx += sub.length();
}
return count;
}
Apache Commons Lang 的StringUtils.countMatches()
是解決我們問題的最簡潔的選項,儘管它需要外部相依性。
8. 結論
在本文中,我們探討了多種方法來計算 Java String.
每種方法都有其優勢,具體選擇取決於專案的具體情況。如果性能和簡潔性至關重要, indexOf()
通常是最佳選擇。如果我們需要正規表示式的彈性, Matcher.find()
或流處理會更勝一籌。而當使用外部函式庫時, StringUtils.countMatches()
可以節省我們的時間。
透過了解所有這些方法,每當我們需要計算 Java 中的子字串出現次數時,我們就能更好地選擇合適的工具。
與往常一樣,範例的完整原始程式碼可在 GitHub 上找到。