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

星號和加號的重複

已經介紹過一個重複運算子或量詞:問號。它告訴引擎嘗試比對前一個代幣 0 次或 1 次,實際上讓它變成可選的。

星號或加號告訴引擎嘗試比對前一個代幣 0 次或多次。加號告訴引擎嘗試比對前一個代幣 1 次或多次。 <[A-Za-z][A-Za-z0-9]*> 比對不帶任何屬性的 HTML 標籤。尖括號是字面值。第一個字元類別比對一個字母。第二個字元類別比對一個字母或數字。星號重複第二個字元類別。因為我們使用了星號,所以第二個字元類別比對不到任何東西也是可以的。因此,我們的正規表示式會比對像 <B> 這樣的標籤。在比對 <HTML> 時,第一個字元類別會比對 H。星號會讓第二個字元類別重複 3 次,在每個步驟中比對 TML

我也可以使用 <[A-Za-z0-9]+>。我沒有這樣做,因為這個正規表示式會配對 <1>,這不是一個有效的 HTML 標籤。但如果你知道你正在搜尋的字串不包含任何此類無效標籤,這個正規表示式可能就足夠了。

限制重複

有一個額外的量詞允許你指定一個代碼可以重複多少次。語法是 {最小值,最大值},其中 最小值 是零或正整數,表示最少配對次數,而 最大值 是等於或大於 最小值 的整數,表示最多配對次數。如果逗號存在但省略了 最大值,則最多配對次數是無限的。所以 {0,1}? 相同,{0,}* 相同,而 {1,}+ 相同。省略逗號和 最大值 會告訴引擎重複代碼恰好 最小值 次。

你可以使用 \b[1-9][0-9]{3}\b 來配對介於 1000 到 9999 之間的數字。\b[1-9][0-9]{2,4}\b 配對介於 100 到 99999 之間的數字。請注意使用 字詞邊界

小心貪婪!

假設你想要使用正規表示式來配對 HTML 標籤。你知道輸入會是一個有效的 HTML 檔案,所以正規表示式不需要排除任何無效使用尖括號的情況。如果它位於尖括號之間,它就是一個 HTML 標籤。

大多數剛接觸正規表示式的人會嘗試使用 <.+>。當他們在像 這是個 <EM>第一個</EM> 測試 的字串上測試它時,他們會感到驚訝。你可能會期望正規表示式配對 <EM>,然後在該配對後繼續配對 </EM>

但它沒有。正規表示式會配對 <EM>第一個</EM>。顯然不是我們想要的。原因是加號是貪婪的。也就是說,加號會導致正規表示式引擎盡可能重複前一個代碼。只有當這導致整個正規表示式失敗時,正規表示式引擎才會回溯。也就是說,它會回到加號,讓它放棄最後一次迭代,並繼續執行正規表示式的其餘部分。讓我們深入了解正規表示式引擎,詳細了解它是如何運作的,以及為什麼這會導致我們的正規表示式失敗。在那之後,我會向你展示兩個可能的解決方案。

與加號一樣,星號和使用大括號的重複都是貪婪的。

深入探討 Regex 引擎

Regex 中的第一個符號是 <。這是一個 字面。正如我們所知,它將匹配的第一個位置是字串中的第一個 <。下一個符號是句點,它匹配除了換行符號之外的任何字元。句點會被加號重複。加號是貪婪的。因此,引擎會盡可能重複句點。句點匹配 E,因此 Regex 會繼續嘗試使用下一個字元匹配句點。M 已匹配,而句點會再重複一次。下一個字元是 >。現在你應該看出問題了。句點匹配 >,而引擎會繼續重複句點。句點將匹配字串中所有剩餘的字元。當引擎到達字串結束後的空隙時,句點會失敗。只有在這個時候,Regex 引擎才會繼續下一個符號:>

到目前為止,<.+ 已匹配 <EM>first</EM> test,而引擎已到達字串的結尾。> 無法在此處匹配。引擎會記住加號重複句點的次數比要求的次數多。(請記住,加號要求句點只匹配一次。)引擎不會承認失敗,而是會回溯。它會將加號的重複次數減少一次,然後繼續嘗試 Regex 的其餘部分。

因此,.+ 的匹配已減少為 EM>first</EM> tes。Regex 中的下一個符號仍然是 >。但現在字串中的下一個字元是最後一個 t。同樣地,它們無法匹配,導致引擎進一步回溯。到目前為止的總匹配已減少為 <EM>first</EM> te。但 > 仍然無法匹配。因此,引擎會繼續回溯,直到 .+ 的匹配減少為 EM>first</EM。現在,> 可以匹配字串中的下一個字元。Regex 中的最後一個符號已匹配。引擎報告 <EM>first</EM> 已成功匹配。

請記住,Regex 引擎急於回傳匹配。它不會繼續進一步回溯以查看是否有另一個可能的匹配。它會報告找到的第一個有效匹配。由於貪婪,這是最左邊最長的匹配。

懶惰而非貪婪

解決此問題的快速方法是讓加號變懶惰而非貪婪。懶惰量詞有時也稱為「非貪婪」或「不情願」。你可以在 regex 中的加號後加上問號來做到這一點。你也可以對星號、大括號和問號本身執行相同的操作。因此,我們的範例會變成 <.+?>。讓我們再次查看 regex 引擎內部。

同樣地,< 會比對字串中的第一個 <。下一個代幣是句點,這次由一個懶惰的加號重複。這會告訴 regex 引擎盡可能少重複句點。最小值為一。因此,引擎會將句點與 E 比對。需求已滿足,引擎會繼續進行 >M。這會失敗。同樣地,引擎會回溯。但這次,回溯會強迫懶惰的加號擴展,而不是減少其範圍。因此,.+ 的比對會擴展為 EM,而引擎會再次嘗試繼續進行 >。現在,> 已成功比對。regex 中的最後一個代幣已比對。引擎會報告已成功比對 <EM>。這更像它。

懶惰的替代方案

在這種情況下,有一個比讓加號變懶惰更好的選項。我們可以使用貪婪的加號和否定字元類別<[^>]+>。這樣更好的原因是因為回溯。當使用懶惰的加號時,引擎必須對它嘗試比對的 HTML 標籤中的每個字元進行回溯。當使用否定字元類別時,當字串包含有效的 HTML 程式碼時,根本不會發生回溯。回溯會減慢 regex 引擎的速度。當在文字編輯器中進行單一搜尋時,你不會注意到差異。但當你在編寫的腳本中或在 EditPad Pro 的自訂語法著色方案中重複使用此類 regex 時,你會節省大量的 CPU 週期。

只有regex 導向引擎會回溯。文字導向引擎不會,因此不會受到速度懲罰。但它們也不支援懶惰量詞。

重複 \Q…\E 逸出序列

序列 \Q…\E 會跳脫字元字串,並將其視為字面字元進行比對。跳脫後的字元會被視為個別字元。如果您在 \E 後面加上量詞,它只會套用於最後一個字元。例如,如果您將 \Q*\d+*\E+ 套用於 *\d+**\d+*,比對結果將會是 *\d+**。只有星號會重複。Java 4 和 5 有個錯誤,會導致整個 \Q…E 序列重複,並將整個主旨字串作為比對結果。此錯誤已在 Java 6 中修正。