在 Java 中獲取正則表達式匹配後的文本
一、概述
在 Java 中處理文本數據時,通常需要使用正則表達式(也稱為 Regex)提取特定的信息片段。但是,僅僅匹配正則表達式模式並不總是足夠的。有時,我們可能需要提取正則表達式匹配後的文本。
在本教程中,我們將探討如何在 Java 中實現這一點。
二、問題簡介
首先,讓我們通過一個例子來快速理解問題。假設我們有一個字符串變量INPUT1
:
static String INPUT1 = "Some text, targetValue=Regex is cool";
以INPUT1
為輸入,我們的目標是得到“ targetValue=
”後面的文字,也就是“ Regex is cool
”。
因此,在這個例子中,如果我們編寫一個 Regex 模式來匹配“ targetValue=
”,我們必須在匹配之後提取所有內容。但是,該問題可能有變體。那麼,讓我們看看另一個輸入變量:
static String INPUT2 = "Some text. targetValue=Java is cool. some other text";
如上面的INPUT2
示例所示,我們在輸入文本中仍然有“ targetValue=
”。但是,這次我們不想在比賽之後獲得一切。相反,我們想在比賽結束後從文本中提取“ Java is cool
”。換句話說,我們需要比賽之後直到第一節的文本。好吧,在實踐中,句點字符可以是各種模式。
接下來,我們將探索解決問題的不同方法。當然,我們將涵蓋INPUT1
和INPUT2
兩種情況。
我們將使用單元測試斷言來驗證解決方案是否可以提取預期結果。此外,為簡單起見,我們將跳過輸入驗證部分,例如檢查輸入字符串是否包含 Regex 模式。
所以現在,讓我們看看他們的行動。
3. 使用split()
方法
標準的split()
方法允許我們通過分隔符將一個字符串拆分為多個字符串作為一個數組。此外,分隔符可以是正則表達式模式。
因此,要解決INPUT1
問題,我們可以簡單地使用“ targetValue=
”作為模式來拆分輸入字符串。然後,結果數組中的第二個元素將是結果:
"Some text, targetValue=Regex is cool" ---split by "targetValue="--> [ "Some text, ", "Regex is cool" ]
現在,讓我們實現這個想法並檢查它是否有效:
String result1 = INPUT1.split("targetValue=")[1];
assertEquals("Regex is cool", result1);
如果我們試一試,測試就會通過。因此,“split and take”解決了INPUT1
問題。
在上面的測試中,我們直接訪問數組元素而不先檢查長度。這是因為我們假設我們的輸入為簡單起見是有效的,正如我們之前提到的。但是,如果我們在實際項目中工作,最好在訪問數組元素之前檢查長度以避免ArrayIndexOutOfBandsException
。
接下來,讓我們看一下INPUT2
情況。解決該問題的一個想法是使用“ targetValue=
”或文字點作為split()
方法的正則表達式模式。然後,我們仍然可以從數組結果中取出第二個元素。
但是,這個想法對我們的INPUT2
不起作用,因為輸入在“ targetValue=
”之前有另一個點: INPUT2 = “Some text. targetValue=…”.
如果我們調用“ targetValue=
” pattern1
和“ .
” 字符pattern2,
在現實世界中,我們無法預測在pattern1
之前的文本中存在多少個pattern2
匹配項。因此,簡單的“分而治之”方法在這裡行不通。
但是,我們可以將輸入拆分兩次以獲得目標值:
"Some text. targetValue=Java is cool. some other text"
Split by "targetValue=" ->
[ "Some text. ", "Java is cool. some other text" ]
Take the second element and split by "." ->
[ "Java is cool", " some other text" ]
The first element is the result
那麼接下來,讓我們在測試中應用這種方法:
String afterFirstSplit = INPUT2.split("targetValue=")[1];
assertEquals("Java is cool. some other text", afterFirstSplit);
String result2 = afterFirstSplit.split("[.]")[0];
assertEquals("Java is cool", result2);
值得一提的是,句點字符在 Regex 中有特殊含義(匹配任何字符)。因此,在第二次split()
調用中, afterFirstSplit.split(“[.]”)[0]
中,我們必須將句點字符放入字符類中或將其轉義(“ \\.
”) 。否則,每個字符都會成為split()
方法的分隔符,我們將得到一個空數組:
// if we use the dot as the regex for splitting, the result array is empty
String[] splitByDot = INPUT2.split("targetValue=")[1].split(".");
assertEquals(0, splitByDot.length);
4. 使用replaceAll()
方法
與split()
方法一樣, replaceAll()
方法也支持 Regex 模式。我們可以使用replaceAll()
將我們不需要的文本替換為空字符串以獲得預期的結果。
例如,要解決INPUT1
問題,我們可以將“targetValue=”
(包括)之前的所有內容替換為空字符串:
String result1 = INPUT1.replaceAll(".*targetValue=", "");
assertEquals("Regex is cool", result1);
與split()
解決方案類似,我們可以調用兩次replaceAll()
方法來解決INPUT2
問題:
String afterFirstReplace = INPUT2.replaceAll(".*targetValue=", "");
assertEquals("Java is cool. some other text", afterFirstReplace);
String result2 = afterFirstReplace.replaceAll("[.].*", "");
assertEquals("Java is cool", result2);
5.使用捕獲組
Java Regex API 允許我們在模式中定義捕獲組。 Regex 引擎會將索引號附加到捕獲組,以便我們可以使用這些索引反向引用組。
接下來,讓我們看看如何使用捕獲組來解決INPUT1
問題:
Pattern p1 = Pattern.compile("targetValue=(.*)");
Matcher m1 = p1.matcher(INPUT1);
assertTrue(m1.find());
String result1 = m1.group(1);
assertEquals("Regex is cool", result1);
正如我們在上面的測試中看到的,我們已經創建了正則表達式模式“ targetValue=(.*)
”。因此,“ targetValue=
”之後的所有內容都在捕獲組中。此外,由於這是模式中的第一組,它的索引號為 1。因此,在調用Pattern.matcher()
之後,我們可以通過調用**matcher.group(1)** .
對於INPUT2
情況,我們不會將“targetValue=”
之後的所有內容都放入組中。相反,我們可以使用 nor 字符類“ [^.]*
”使該組包含第一個句點之前的所有內容。接下來,讓我們看看它的實際效果:
Pattern p2 = Pattern.compile("targetValue=([^.]*)");
Matcher m2 = p2.matcher(INPUT2);
assertTrue(m2.find());
String result2 = m2.group(1);
assertEquals("Java is cool", result2);
或者,我們可以使用非貪婪量詞“ *?'
實現相同的目標:
Pattern p3 = Pattern.compile("targetValue=(.*?)[.]");
Matcher m3 = p3.matcher(INPUT2);
assertTrue(m3.find());
String result3 = m3.group(1);
assertEquals("Java is cool", result3);
當我們處理INPUT2
情況時, split()
和replaceAll()
方法需要兩個步驟來完成這項工作。正如我們所見,使用 Regex 的捕獲組,我們可以一次性解決INPUT2
問題。
6.使用環視斷言
Java Regex API 支持環視斷言。當我們想要基於其周圍字符匹配模式而不實際將這些字符包含在匹配中時,環視斷言很有用。
接下來,讓我們探討如何使用環視斷言解決INPUT1
案例:
Pattern p1 = Pattern.compile("(?<=targetValue=).*");
Matcher m1 = p1.matcher(INPUT1);
assertTrue(m1.find());
String result1 = m1.group();
assertEquals("Regex is cool", result1);
正如我們在上面的代碼中看到的,我們在 Regex 模式中使用了一個積極的回顧斷言:“ (?<=targetValue=).*
” 。它匹配出現在字符串“ targetValue=
”之後的任何字符。
同樣,我們可以改變“。”到 nor 字符類“ [^.]
”來解決INPUT2
情況:
Pattern p2 = Pattern.compile("(?<=targetValue=)[^.]*");
Matcher m2 = p2.matcher(INPUT2);
assertTrue(m2.find());
String result2 = m2.group();
assertEquals("Java is cool", result2);
或者,我們可以同時使用積極的後向斷言和積極的先行斷言來提取我們需要的文本:
Pattern p3 = Pattern.compile("(?<=targetValue=).*(?=[.])");
Matcher m3 = p3.matcher(INPUT2);
assertTrue(m3.find());
String result3 = m3.group();
assertEquals("Java is cool", result3);
在上面的代碼中:
-
(?<=targetValue=)
是我們在解決INPUT1
問題時看到的正後視斷言。 -
(?=[.])
是積極的前瞻斷言。
因此, “ (?<=targetValue=).*(?=[.])
”匹配“ targetValue=
”和句點字符之間的任何字符,這正是我們想要的結果。
七、結論
在本文中,我們研究了正則表達式匹配後提取文本問題的兩種變體。一個在匹配的正則表達式之後返回所有內容,另一個在一個正則表達式匹配之後但在第二個不同的正則表達式匹配之前返回所有內容。
此外,我們通過示例學習了四種不同的方法來解決這兩種情況。
與往常一樣,此處提供的所有代碼片段都可以在 GitHub 上找到。