簡體   English   中英

為什么帶有正則表達式 ^[az]$ 的 find() 不等於帶有正則表達式 [az] 的 match()?

[英]Why is find() with Regex ^[a-z]$ not equivalent to matches() with Regex [a-z]?

Java 的Matcher是通過解釋Pattern (正則表達式)對字符序列執行匹配操作的引擎。 這個 class 有兩個眾所周知的操作:

換句話說, find()應該用於匹配 substring,而matches()應該用於匹配整個輸入。 這讓我想到將find()^[az]$之類的正則表達式一起使用等同於將matches()[az]之類的正則表達式一起使用,因此我繼續進行了測試。

單擊此處在線運行以下代碼。

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");
    }
}

其中產生了以下output:

        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\ra\r\na\n都失敗了:

  • 在這些情況下使用matches()[az]+會產生false 顯然,最后的換行符算作一個字符,未通過測試。
  • 在這些情況下使用find()^[az]+$會產生true 顯然最后的換行符被忽略,通過了測試。

這僅在換行符位於末尾而不是開頭時才成立,因為兩種方法都將\r\na視為相同。

這是怎么回事?

恭喜,您剛剛在 JDK 中發現了一個錯誤

或者好吧——他們還不確定這是一個錯誤。 從這些其他報告來看,它至少令人困惑: JDK-8059325 JDK-8058923 JDK-8049849 JDK-8043255

根據Pattern 的文檔

默認情況下,正則表達式 ^ 和 $ 忽略行終止符,僅分別匹配整個輸入序列的開頭和結尾。 如果 MULTILINE 模式被激活,則 ^ 在輸入的開頭和除輸入結尾之外的任何行終止符之后匹配。 在 MULTILINE 模式下,$ 在行終止符或輸入序列的結尾之前匹配。

在您的情況下,您沒有使用 MULTILINE 模式,因此我將其解釋為它應該忽略行終止符。 a\ra\r\na\n的情況下它實際上做了什么,回答你的問題意味着:不,這兩個測試不等價,因為它們以不同的方式處理換行符。

我希望案例\r\na\na會產生相同的結果 - 但他們沒有。

^$表示不同的東西,具體取決於您運行正則表達式的模式。請參閱Pattern.MULTILINE標志的 javadoc。

無論如何, ^$從不消耗任何東西。

正則表達式引擎的工作方式是,正則表達式中的所有內容都可以“匹配”或“不匹配”,通常作為匹配的一部分,它們也會消耗字符。

You can think about it as a cursor that, just like your text cursor is always in between characters, and the regexp engine will go from left to right through your regexp, starting the cursor at the beginning of input, and for each item in the正則表達式模式,該項目匹配或失敗,通常但不總是,將 cursor 向前移動。

^$可以匹配或失敗,但它們不能移動 cursor。 它與例如\b (匹配“分詞”)或(正面/負面)look-(ahead/behind) 以這種方式相同。 這里的相關技巧是,對於matches()情況,必須消耗每個字符 - 匹配過程必須結束,以便 cursor 處於最后。 您的模式只能使用小寫字母(只有在有小寫字母時才轉發 cursor),所以當您在字符串中拋出任何不是其中之一的字符時(所以即使是一個\r\n ,在任何位置) ,它不可能匹配; 沒有辦法使用這些非小寫字符。

另一方面,使用find() ,您不需要消耗所有字符; 你只需要一個 substring 來匹配,就是這樣。

然后我們得到:字符串中的哪些“狀態”被認為是“匹配” ^ state,哪些被認為是“匹配” $ state。 答案部分取決於MULTILINE模式是否打開。 它在您的代碼片段中關閉; 您可以通過使用Pattern.compile(patternString, Pattern.MULTILINE)制作您的正則表達式來打開它,或者通過在您的正則表達式字符串中扔(?m)來打開它( (?xyz)從您的模式中顯示的點啟用/禁用標志字符串,否則無效(始終匹配,不消耗任何內容 - 這是 regexp-engine-ese 用於:不做任何事情)。

甚至UNIX_LINES對此產生影響(在UNIX_LINES模式下,只有\n被視為行終止符,如果您處於 MULTILINE 模式,則^ / $將在您處於行終止符時匹配。

在多行模式下,您的所有示例都可以輕松匹配; ^在任何時候 cursor 處於輸入開始時為“真”(cursor 始終在字符之間;如果它在開始和第一個字符之間(即在第一個字符之前),則認為它匹配)-或者,如果您介於換行符和緊隨其后的內容之間,只要該內容不是整個輸入的結尾即可。 \r\n都計數(因為UNIX_LINES已關閉)。

但是您沒有處於多線模式,那么大火中發生了什么?

發生的事情是文檔是錯誤的。 正如@MartinDevillers 出色地挖掘相關錯誤條目所示。

文檔只是略有錯誤。 具體來說,正則表達式引擎試圖變得更聰明一點,而不是死記硬背:

從正則表達式 package 的javadoc

默認情況下,這些表達式只匹配整個輸入序列的開頭和結尾。

這只是純粹的廢話。 它比這更智能:當您的 cursor 介於一個字符和一個換行符之間時,它們也會匹配,盡管\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());

這會打印出 false、true、true、true、false。 中間 3 的末尾都有一個字符(或多個字符),在至少一個主要操作系統上被認為是“單個換行符”( \n是 posix/unix/macosx, \r\n是 windows, \r是經典 mac我不認為它曾經運行過 JVM,並且沒有人再使用它,但我猜出於祖父的原因,它仍然被大多數規則視為“換行符”)。

這就是你在這里所缺少的。

結論:

文檔略有錯誤,並且$比僅僅“在輸入的最后匹配”更聰明; 它承認有時輸入的末尾有一個雜散的換行符,並且$不會因此而感到困惑。 但是, matches()會在最后被懸空的換行符弄糊塗——它必須消耗所有東西,否則它不被認為是匹配的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM