快速入門
教學課程
工具和語言
範例
參考
書籍評論
Regex 教學課程
簡介
目錄
特殊字元
不可列印字元
Regex 引擎內部
字元類別
字元類別減法
字元類別交集
簡寫字元類別
錨點
字詞邊界
交替
選項項目
重複
分組和擷取
反向參照
反向參照,第 2 部分
命名群組
相對反向參照
分支重設群組
自由間距和註解
Unicode
模式修改器
原子群組
獨佔量詞
前瞻和後顧
環顧,第 2 部分
將文字保留在比對之外
條件式
平衡群組
遞迴
子常式
無限遞迴
遞迴和量詞
遞迴和擷取
遞迴和反向參照
遞迴和回溯
POSIX 方括號表示式
零長度比對
持續比對
本網站上的更多資訊
簡介
正規表示式快速入門
正規表示式教學課程
取代字串教學課程
應用程式和語言
正規表示式範例
正規表示式參考
取代字串參考
書籍評論
可列印 PDF
關於本網站
RSS 饋送和部落格
RegexBuddy—Better than a regular expression tutorial!

獨佔量詞

重複運算子或量詞 的主題說明貪婪和非貪婪重複之間的差異。貪婪和非貪婪決定 regex 引擎嘗試 regex 模式的可能排列順序。貪婪量詞會先嘗試盡可能重複令牌,並在引擎回溯尋找整體比對時逐漸放棄比對。非貪婪量詞會先盡可能少重複令牌,並在引擎回溯 regex 尋找整體比對時逐漸擴充比對。

由於貪婪和非貪婪會改變嘗試排列的順序,因此它們可能會改變整體 regex 比對。不過,它們不會改變 regex 引擎會回溯嘗試正規表示式的所有可能排列的事實,以防找不到任何比對。

獨佔量詞是一種防止 regex 引擎嘗試所有排列的方法。這主要用於效能原因。您也可以使用獨佔量詞來消除某些比對。

在本文討論的正規表示式風格中,JGsoftJavaPCRE 支援佔有量詞。這包含基於 PCRE 的正規表示式支援語言,例如 PHPDelphiRPython 從 Python 3.11 開始支援佔有量詞,Perl 從 Perl 5.10 開始支援,Ruby 從 Ruby 1.9 開始支援,Boost 從 Boost 1.42 開始支援。

佔有量詞的工作原理

與貪婪量詞一樣,佔有量詞會盡可能重複令牌。與貪婪量詞不同的是,它不會在引擎回溯時放棄比對。使用佔有量詞時,只有全部比對或完全不比對兩種結果。您可以在量詞後加上一個額外的 + 來讓量詞變成佔有量詞。 * 是貪婪的,*? 是非貪婪的,*+ 是佔有的。 ++?+{n,m}+ 都是佔有的。

讓我們看看如果我們嘗試將 "[^"]*+""abc" 進行比對會發生什麼事。 " 會比對到 "[^"] 會比對到 abc,因為它會被 星號 重複。最後的 " 然後會比對到最後一個 ",我們就找到一個整體比對。在這種情況下,無論我們使用貪婪量詞或佔有量詞,最終結果都是一樣的。不過,佔有量詞確實會稍微提升效能,因為它不需要記住任何回溯位置。

在正規表示式失敗的情況下,效能提升可能會很顯著。如果主旨是 "abc(沒有閉合引號),上述比對過程會以相同的方式進行,只不過第二個 " 會失敗。當使用佔有量詞時,沒有任何步驟需要回溯。正規表示式沒有任何交替或非佔有量詞可以放棄部分比對,以嘗試正規表示式的不同排列組合。因此,當第二個 " 失敗時,比對嘗試會立即失敗。

如果我們使用 "[^"]*" 搭配貪婪量詞,引擎將會回溯。在 " 在字串結尾失敗後,[^"]* 會放棄一個比對,留下 ab。然後 " 將無法比對 c[^"]* 回溯到只有 a,而 " 無法比對 b。最後,[^"]* 回溯到比對零個字元,而 " 無法比對 a。只有在這個時候,所有回溯位置都已用盡,引擎才會放棄比對嘗試。基本上,這個正規表示法會執行與未比對的開頭引號後面的字元數量一樣多的不必要步驟。

佔有量詞何時重要

佔有量詞的主要實用好處是加速你的正規表示法。特別是,佔有量詞讓你的正規表示法可以更快失敗。在上面的範例中,當結尾引號無法比對時,我們知道正規表示法不可能跳過引號。因此不需要回溯並檢查引號。我們透過讓量詞佔有來讓正規表示法引擎知道這一點。事實上,有些引擎,包括 JGsoft 引擎,在編譯你的正規表示法時會偵測到 [^"]*" 是互斥的,並自動讓星號佔有。

現在,像具有單一量詞的正規表示法一樣的線性回溯相當快。你不太可能注意到速度差異。然而,當你巢狀量詞時,佔有量詞可能會救你一命。巢狀量詞表示你在群組內有一個或多個重複的記號,而且群組也會重複。這就是 災難性回溯 經常抬頭的時候。在這種情況下,你將依賴佔有量詞和/或 原子群組 來拯救局面。

佔有量詞可以改變比對結果

使用佔有量詞可以改變比對嘗試的結果。由於沒有回溯,而且需要貪婪量詞回溯的比對將無法使用佔有量詞找到。例如,".*""abc"x 中比對到 "abc",但 ".*+" 完全無法比對到這個字串。

在兩個正規表示式中,第一個 " 符合字串中的第一個 "。然後重複的點符合字串 abc"x 的其餘部分。然後第二個 " 無法符合字串的結尾。

現在,兩個正規表示式的路徑開始分歧。佔有的點星號想要全部。不會進行回溯。由於 " 失敗,因此沒有任何排列組合可以嘗試,而且整體符合嘗試失敗。貪婪的點星號雖然一開始抓取所有東西,但願意放棄。它將一次回溯一個字元。回溯到 abc"" 無法符合 x。回溯到 abc" 符合 "。找到整體符合 "abc"

基本上,這裡的教訓是,使用佔有量詞時,您需要確保您套用佔有量詞的任何內容都無法符合其後面的內容。上述範例中的問題是,點也符合結束引號。這讓我們無法使用佔有量詞。前一節中的否定字元類別無法符合結束引號,因此我們可以讓它變成佔有的。

使用原子群組取代佔有量詞

技術上,佔有量詞是一種記號便利性,用於將 原子群組 放在單一量詞周圍。支援佔有量詞的所有正規表示式風格也支援原子群組。但並非所有支援原子群組的正規表示式風格都支援佔有量詞。使用這些風格,您可以使用原子群組達成完全相同的結果。

基本上,寫 (?>X*),取代 X*+。重要的是,量化的代幣 X 和量詞都位於原子群組內。即使 X 是群組,您仍需要在它周圍放置一個額外的原子群組,才能達到相同的效果。 (?:a|b)*+ 等於 (?>(?:a|b)*),但不等於 (?>a|b)*。後者是一個有效的正規表示式,但當用作較大正規表示式的一部分時,它不會產生相同的效果。

舉例來說,(?:a|b)*+b(?>(?:a|b)*)b 都無法比對 ba|b 比對 b。星號得到滿足,而它具有所有格或原子群組的事實,將導致星號忘記所有回溯位置。正規表示式中的第二個 b 沒有任何東西可以比對,而整體比對嘗試失敗。

在正規表示式 (?>a|b)*b 中,原子群組強制交替放棄其回溯位置。這表示如果比對到 a,如果正規表示式的其餘部分失敗,它不會再回來嘗試 b。由於星號位於群組之外,因此它是一個正常的貪婪星號。當第二個 b 失敗時,貪婪星號回溯到零次反覆。然後,第二個 b 比對主旨字串中的 b

當將使用所有格量詞的正規表示式轉換為沒有所有格量詞的正規表示式風格時,這個區別特別重要。當然,您可以讓 RegexBuddy 等工具為您進行轉換。