PHP Conference Japan 2024

只匹配一次的子模式

在最大化和最小化重複匹配的情況下,如果後續匹配失敗,通常會導致重新評估重複項目,以查看不同的重複次數是否允許模式的其餘部分匹配。有時,阻止這種情況很有用,可以改變匹配的性質,或者在模式的作者知道沒有必要繼續時,使其比其他情況下更早失敗。

例如,考慮模式 \d+foo 應用於主體字串 123456bar

在匹配所有 6 個數字然後未能匹配「foo」之後,匹配器的正常動作是再次嘗試只用 5 個數字匹配 \d+ 項目,然後是 4 個,依此類推,直到最終失敗。只匹配一次的子模式提供了一種方法來指定一旦模式的一部分匹配,就不會以這種方式重新評估,因此匹配器在第一次未能匹配「foo」時會立即放棄。該表示法是另一種特殊括號,以 (?> 開頭,如本例所示:(?>\d+)bar

這種括號會將它包含的模式部分在匹配後「鎖定」,防止模式中後續的失敗回溯到其中。然而,回溯到它之前的項目則會正常運作。

另一種描述是,這種子模式會匹配與一個相同的獨立模式在主字符串當前位置錨定時所匹配的字符串。

一次性子模式不是捕獲型子模式。像上面例子中的簡單情況可以被認為是一個最大化重複,它必須吞噬它所能吞噬的一切。因此,雖然 \d+ 和 \d+? 都準備調整它們匹配的數字數量以使模式的其餘部分匹配,但 (?>\d+) 只能匹配整個數字序列。

這種結構當然可以包含任意複雜的子模式,並且可以嵌套。

一次性子模式可以與反向斷言結合使用,以指定在主字符串末尾的高效匹配。考慮一個簡單的模式,例如 abcd$,當應用於一個不匹配的長字符串時。由於匹配是從左到右進行的,PCRE 將在主字符串中查找每個 "a",然後查看後面的內容是否與模式的其餘部分匹配。如果模式指定為 ^.*abcd$,則最初的 .* 會首先匹配整個字符串,但當這失敗時(因為後面沒有 "a"),它會回溯以匹配除最後一個字符以外的所有字符,然後是除最後兩個字符以外的所有字符,依此類推。再一次,對 "a" 的搜索涵蓋了整個字符串,從右到左,所以我們並沒有好轉。然而,如果模式寫成 ^(?>.*)(?<=abcd),則 .* 項目就沒有回溯的可能;它只能匹配整個字符串。後續的反向斷言對最後四個字符進行單次測試。如果失敗,匹配立即失敗。對於長字符串,這種方法會對處理時間產生顯著影響。

當一個模式在一個可以無限次重複的子模式中包含一個無限重複時,使用一次性子模式是避免某些失敗匹配花費很長時間的唯一方法。模式 (\D+|<\d+>)*[!?] 匹配無限數量的子字符串,這些子字符串由非數字或包含在 <> 中的數字組成,後跟 ! 或 ?。當它匹配時,它運行得很快。但是,如果將其應用於 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa,則需要很長時間才能報告失敗。這是因為字符串可以在兩個重複之間以多種方式分割,並且必須嘗試所有方式。(示例使用 [!?] 而不是末尾的單個字符,因為 PCRE 和 Perl 都有一個優化,允許在使用單個字符時快速失敗。它們會記住匹配所需的最後一個單個字符,如果字符串中不存在該字符,則會提前失敗。)如果將模式更改為 ((?>\D+)|<\d+>)*[!?],則非數字序列無法被打破,並且失敗會很快發生。

新增備註

使用者提供的備註 1 則備註

4
匿名
2 年前
千萬不要在「單次子模式」(?>...) 裡使用「單行」註解 (#, //)。
我花了幾乎一整天才解決這個問題。
請改用 'C' 風格的註解!

PHP 手冊說明,「單行」註解 (#, //) 會在 "?>" (PHP 結束標籤) 之前結束。

在「單行」註解 (#, //) 中的 "?>" 似乎會被視為 PHP 結束標籤。

<?php

/* (?> */
echo '"C" 風格註解可以正常運作!<br>';

# (?>
echo '"單行" 註解';

?>
To Top