JavaMatcher
是通過解釋Pattern
(正則運算式)對字符序列執行匹配操作的引擎。這個類有兩個眾所周知的操作:
Matcher.find()
它掃描輸入序列以尋找與模式匹配的下一個子序列。Matcher.matches()
它嘗試將整個輸入序列與模式匹配。
換句話說,find()
應該用于匹配子字串而matches()
應該用于匹配整個輸入。這讓我認為find()
與 Regex like^[a-z]$
一起使用等同于matches()
與 Regex like一起使用[a-z]
,所以我繼續進行了測驗。
單擊此處在線運行以下代碼。
import java.util.List;
import java.util.regex.Pattern;
public class Main
{
public static void main(String[] args) {
Pattern sub = Pattern.compile("[a-z] ");
Pattern all = Pattern.compile("^[a-z] $");
List<String> tests = List.of("", " ", "a", "A", "abc", "a\r",
"a\r\n", "a\n", " a", "\na", "\ra\n",
"\r\na", "\na");
for (String test : tests) {
boolean matchesSub = sub.matcher(test).matches();
boolean matchesAll = all.matcher(test).find();
System.out.printf("%s\t%s\t%s", format(test), matchesSub, matchesAll);
System.out.println();
}
}
private static String format(String input) {
return input.replace("\r", "\\r").replace("\n", "\\n");
}
}
這產生了以下輸出:
false false
false false
a true true
A false false
abc true true
a\r false true
a\r\n false true
a\n false true
a false false
\na false false
\ra\n false false
\r\na false false
\na false false
有趣的是,這個測驗失敗了a\r
,a\r\n
并且a\n
:
- 在這些情況下使用
matches()
with[a-z]
會產生false
. 顯然,末尾的換行符被視為一個字符,未通過測驗。 - 在這些情況下使用
find()
with^[a-z] $
會產生true
. 顯然,最后的換行符被忽略,通過了測驗。
這僅在換行符在末尾時才適用,而不是在開頭,因為\r\na
這兩種方法的處理方式相同。
這是怎么回事?
uj5u.com熱心網友回復:
^
并$
根據您運行正則運算式的模式表示不同的內容。請參閱Pattern.MULTILINE
標志的 javadoc。
在任何情況下,^
和$
從來沒有任何消耗。
正則運算式引擎的作業方式是,正則運算式中的所有內容都可以“匹配”或“不匹配”,并且通常作為匹配的一部分,它們也使用字符。
你可以把它想象成一個游標,就像你的文本游標總是在字符之間一樣,正則運算式引擎會從左到右通過你的正則運算式,從輸入的開頭開始游標,對于regexp 模式,該專案要么匹配要么失敗,通常但不總是向前移動游標。
^
并且$
可以匹配或失敗,但它們不能移動游標。它與例如\b
(匹配“斷字”)或(正/負)以這種方式查看(向前/向后)相同。這里的相關技巧是,對于這種matches()
情況,每個字符都必須被消耗——匹配程序必須結束,以便游標位于最后。您的模式只能使用小寫字母(只有在有小寫字母時才向前移動游標),因此當您在字串中拋出任何不是這些字符之一的字符時(即使是一個\r
或\n
,在任何位置),它也不能' t 可能匹配;沒有辦法使用這些非小寫字符。
使用find()
,另一方面,您不需要消耗所有字符;你只需要一個子串來匹配,就是這樣。
然后讓我們知道:字串中的哪些“狀態”被認為是“匹配”^
狀態,哪些被認為是“匹配”$
狀態。答案部分取決于MULTILINE
模式是否打開。它在您的代碼片段中關閉;你可以通過使用你的正則運算式來打開它Pattern.compile(patternString, Pattern.MULTILINE)
,或者通過(?m)
在你的正則運算式字串中折騰((?xyz)
從模式字串中顯示的點啟用/禁用標志,否則無效(總是匹配,不消耗任何東西 - 那是正則運算式引擎-ese 用于:不做任何事情)。
甚至UNIX_LINES
對此有影響(UNIX_LINES
模式打開時,僅\n
被視為行終止,如果您處于 MULTILINE 模式,則在行終止時^
/$
將匹配。
在多行模式下,您的所有示例都非常匹配;^
任何時候游標處于輸入開始處(游標總是在字符之間;如果它在開始和第一個字符之間(即在第一個字符之前),則認為是匹配的)-或如果您位于換行符和緊隨其后的內容之間,只要該內容不是整個輸入的結尾。\r
和\n
所有計數(因為UNIX_LINES
關閉)。
但是你不是在多線模式下,那么大火是怎么回事?
發生的事情是檔案是錯誤的。正如@MartinDevillers 對相關錯誤條目的出色挖掘所顯示的那樣。
檔案只是略有錯誤。具體來說,正則運算式引擎試圖比死記硬背更聰明一點:
來自正則運算式包的javadoc:
默認情況下,這些運算式僅匹配整個輸入序列的開頭和結尾。
這只是簡單的廢話。這是更智能比:他們還當你的游標在性格和只有一個換行符之間的匹配,但任何的\r
,\n
和\r\n
都被認為是“一個換行符”,只要一個換行符是在整個輸入的最后一件事. 換句話說,給定(其中每個空間都不是真實的;我正在騰出空間來顯示游標所在的位置,它只能位于字符之間,因此我可以在它們下方貼上一個標記以顯示匹配的位置):
" h e l l o \r \n "
^ ^ ^
匹配系統認為$
在任何^
地方匹配。讓我們測驗一下這個理論:
Pattern p = Pattern.compile("hello$");
System.out.println(p.matcher("hello\r\n\n").find());
System.out.println(p.matcher("hello\r\n").find());
System.out.println(p.matcher("hello\r").find());
System.out.println(p.matcher("hello\n").find());
System.out.println(p.matcher("hello\n\n").find());
這會列印假,真,真,真,假。中間 3 個在末尾都有一個字符(或多個字符),在至少一個主要作業系統上被認為是“單個換行符”(\n
posix/unix/macosx,\r\n
是 windows,\r
是經典的 mac,我認為它從未運行過)一個 JVM,沒有人再使用了,但我猜出于祖父的原因,它仍然被大多數規則視為“換行符”)。
這就是你在這里所缺少的。
結論:
The docs are slightly wrong, and $
is smarter than merely 'matches at very end of input'; it acknowledges that sometimes input has a stray newline hanging off of the end of it, and $
won't get confused by this. But matches()
will get confused by a dangling newline at the very end though - it has to consume everything or it isn't considered matched.
uj5u.com熱心網友回復:
正如@WiktorStribi?ew在他的評論回答,matches()
與[a-z]
是不是等同于find()
用^[a-z] $
,但它是相當于find()
用^[a-z] \\z
。這是因為$
將單個尾隨換行符視為特殊情況:它會忽略它。\z
不是那么寬容。
這種行為在官方 Java 檔案中沒有明確記錄。此外,目前正在調查的 JDK 中有一個公開的錯誤報告,專門處理$
匹配器、尾隨換行符和find()
方法。此外,從這些其他較舊的報告來看,它至少令人困惑:JDK-8218146 JDK-8059325 JDK-8058923 JDK-8049849 JDK-8043255
最后,這種行為在所有 RegEx 實作中并不相同:
在除 JavaScript 之外的所有主要引擎中,如果字串有一個最后的換行符,則 $ 錨點可以在那里匹配。例如,在 apple\n 中,e$ 匹配最后的 e。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/361193.html
標籤:爪哇 正则表达式 java-8 java-11 java-13
下一篇:從FlaskForm中移除標簽