這個例子出自《精通正則表達式》,做一下筆記幫助理解和記憶。
第一版
最簡單的case就是考慮包含一對引號,那么寫出來的表達式應該是這樣的:
".*"
但是這個未免太簡單了吧,會有啥問題呢?假如輸入的字符串長這樣結果就會出問題拉。see...
Input String: "Hello" and "World"
Regex: ".*"
Match: "Hello" and "World"
為什么會全部匹配到呢?這是因為 * 是一個greedy(匹配優先)的量詞,我覺得英文的意思更容易幫助我們理解。這意味着它會首先會'貪婪'得把所有的字符匹配完,匹配到最后一個字符發現沒有字符可以匹配了,於是開始匹配下一個引號,它會首先回朔到最后一個引號前,然后開始匹配引號匹配引號,發現可以匹配,然后就完成了。這就是為啥整個字符串都被匹配了。
第二版
既然是因為greedy的量詞導致的這個問題,那我們將其給成lazy(忽略優先)的量詞 ----- *? 。當一個量詞是忽略優先的話,那在匹配的時候,引擎會選擇忽略這個忽略優先量詞修飾的字符去匹配下一個字符,如果匹配則繼續,如果不匹配則返回來匹配這個忽略優先量詞修飾的字符。
看一下這個例子,將表達式稍作修改,匹配結果就變鳥。
Input String: "Hello" and "World"
Regex: ".*?"
Match-1: "Hello"
Match-2: "World"
好了,似乎這個版本已經圓滿完成我們的任務啦。
第三版
那我們將需求繼續變化一下,在程序的世界里有一種東西叫做轉移字符,比如有個字符串長這樣子\"Hello, World!\"+\"
。 直接上最終正則表達式。
Input String: \"Hello, World!\"+\"
Regex: "(\\.|[^\\"])*"
Match-1: No Match
正則表達式中關鍵的是這個(\\.|[^\\"])
,這個括號在正則表達式里是起到一個多選結構的作用,表示匹配括號內任意一個子表達式即匹配。這個括號里面由兩部分,第一部分是\\.
,第二部分是[^\\"]
。
\\.
\\.
能夠匹配任何的轉義字符,嚴格來說是匹配任意\以及其后面的字符,即使它們不是真正的轉義字符。
[^\"]
這個輸入字符串最后一個引號是轉移的引號,所以這個正則表達式沒有匹配到任何的結果。這個結果是對的,這個功勞就要歸結到這個[^\\"]
,它的意思是匹配非\"的任意字符。當引擎匹配到字符串最后發現"
還未匹配,於是進行回溯,回溯到▴那個位置\"Hello, World!\"+ ▴ \"
。引擎會嘗試去匹配[^\\"]
,發現匹配失敗,於是失敗!
假如我們將[^\\"]
換成[^"]
,那么在剛才那個位置的時候,[^"]
就會匹配\
字符,然后\
之后的"
會被外層的引號匹配,於是它的結果就是被匹配了。這顯然是很奇怪的,當然如果你想要這么匹配也可以。
其他版本
還有一種方式是利用正則表達式里面的固化分組(Atomic grouping)或者占有優先量詞(Possessive)。固化分組:"(?>(\\.|[^"])*)
,占有優先量詞:"(\\.|[^"])*+"
。在固化分組或者占有優先量詞的匹配過程中,在固化分組內或者占有優先量詞修飾的組內,如果一個字符已經被匹配那么之前的狀態將會被舍棄,就防止它進行回溯。
在這個例子中,\"Hello, World!\"+\"
,當匹配到這個位置的時候(\"Hello, World!\"+\" ▴
)發現沒有辦法繼續匹配下去了,引擎會選擇直接匹配失敗,而不是回溯到之前的備用狀態進行新的匹配。
關鍵的概念
例子雖然簡單,但是出現了正則表達式中很多關鍵的概念,預知詳情,請閱讀《精通正則表達式》
- 匹配優先量詞(Greedy quantifier)
- 忽略優先量詞(Lazy quantifier)
- 占有優先量詞(Possessive quantifier)
- 固化分組(Atomic grouping)
- 字符組(Character Classes)
- 多選結構(Alternation)
本文純屬個人讀書筆記,如有錯誤概不負責喲~~~