匹配溢出問題在正則表達式當中算是比較常見的問題,它常常導致我們匹配結果莫名其妙的出錯,本文專門為你講解如何通過匹配不包含特定字符串的方法來解決這類問題。
那么,什么是匹配溢出呢?
下面我們來看個例子:
源文本:<div>ABC</div><div>123</div>
目標匹配數據:<div>123</div>
正則:<div.*?>\d+</div>
實際匹配:<div>ABC</div><div>123</div>
這個例子,我們匹配的數據偏移了目標匹配數據,但卻包含目標匹配數據,我們就可以認為,前面部分的正則,因為通配符.*?
的限定范圍比較大,對很多字符做了非預期匹配,也就是匹配溢出了。
對于這個問題,我們可以通過降低通配符的限定范圍,從而使得它對于前面部分的匹配因為無法滿足正則而退出,最終匹配到我們的目標數據。
因此,上面正則表達式可以修改為:
<div[^>]*?>\d+</div>
這樣修改之后,通配部分最多只能匹配div標簽里的到達>
前的字符,就不會怕它因為通配匹配掉下一個<div>
標簽了。
因為上面式子中是對>
做了排除匹配,它無論如何也不會超出>
字符的范圍,因此,上面正則從性能角度可以修改為:
<div[^>]*>\d+</div>
因為,假如<div>
標簽當中還有其他的噪點數據如<div class="xxx">
,我們使用非貪婪模式,就會形成多次的回溯,降低性能。此時適當使用貪婪模式,可以讓它里面匹配到<div
之后,>
之前的所有噪點數據,無需回溯,性能會有所提升。
上面例子是針對單字符,使用排除特定字符方法,限定通配符的匹配范圍。而我們應用過程中,還經常會遇見多字符的排除匹配情況,下面我們來看看。
源文本:
http://www.zjmainstay.cn
http://www.baidu.com
http://www.qq.com目標數據:每行一個域名,取出不是百度的域名
正則:/^http:\/\/((?!baidu).)+$/m
匹配結果:
http://www.zjmainstay.cn
http://www.qq.com說明:正則里/m的m表示多行模式
可能很多人看到((?!baidu).)+
就立馬懵逼了。其實,這個表達式也沒那么難,大家可以這么理解:
假設是排除一個單字符>
,至少有一個字符,你是不是會寫:[^>]+
沒錯,那我要求你一定要用否定正向環視去做呢?你會寫成:(?!>).
限制當前位置后面的字符不能是>
,完全沒有問題,上面兩個是等價的。
那么,假設我限定當前位置后面不是baidu
呢?這時候,很明顯對于單字符排除就不太好寫了,因為我們知道正則是單字符匹配的,用單字符匹配就是排除各種b、a、i、d、u
的組合,想想都不可完成。但我們可以用否定正向環視輕松解決:(?!baidu).
那么問題來了,我要匹配這一串字符中每個位置都不能是baidu
,也就是:
(?!baidu).(?!baidu).(?!baidu).(?!baidu).(?!baidu).(?!baidu).(?!baidu).(?!baidu).(?!baidu).
誰知道有幾個位置呢?(上面的(?!baidu).
個數僅做示例)
因此,根據題目,至少會有一個(?!baidu).
吧,因此,正則修改為:
((?!baidu).)+
也就得到了我們上面的式子了。此時的(?!baidu).
其實可以看做一次匹配,就是匹配.
,(?!baidu)
限定它與其后其他字符不能構成baidu
。
而((?!baidu).)
的括號只是把這個整體框起來,用于次數限定而已。
參考資料:
更多關于正則表達式入門的內容,請參考本站博客《我眼里的正則表達式入門教程》
正則表達式高級的內容,請參考本站博客《深入講解正則表達式高級教程》
Windows正則表達式測試工具請從《正則表達式測試工具RegexBuddy-4.1.0》下載
Mac正則表達式測試工具請從《Mac正則表達式測試工具》下載
文章首發自Zjmainstay學習筆記《正則表達式匹配不包含特定字符串解決匹配溢出問題》