正則匹配中的非貪婪匹配不是最短匹配


最近在工作中遇到一個需求,就是找出html中所有錨文字包含 聯系方式 的超鏈接。剛開始我寫了一個很簡單的正則來解決這個問題<a.*?聯系方式.*?</a。但是在測試的時候卻發現這個正則表達式並不像我想象的那樣工作。

圖中給出了一個正則表達式匹配的例子,可以看出在這段文字中有兩個匹配,但是第一個匹配所包含的結果已經超出了實際需要的范圍,包含了太多的超鏈接標簽,而我需要的是最短的匹配也就是圖中橫線畫出的范圍,這是怎么回事?

正則匹配的原理

這要從正則匹配的原理說起,簡單的來說正則匹配是一種貪心的算法。它總是先找到第一個匹配的位置,然后向后繼續匹配其他的表達式符號。對於本文給出的正則表達式,會現在html中找到一個<a標簽,然后之后是.*?,直到找到一個聯系方式,在這個過程中如果找到了另一個<a,會被當作.*?匹配的部分,而忘記了要匹配表達式的開頭就是<a。也就是說,除非發生失配,正則表達式不會主動地回溯。盡管使用了來表達非貪婪匹配,也只能限制向后匹配時盡可能地短,而不能縮短已匹配部分的長度。也就是說,非貪婪匹配向后是最短匹配,但是向前不是最短匹配

對於這個任務,我后來使用了其他效率更高的方法實現了,但是有沒有可能使用正則表達式來完成這個任務呢?

零寬斷言

零寬斷言是一種零寬度的匹配,它匹配到的內容不會保存到匹配結果中去,最終匹配結果只是一個位置而已。
作用是給指定位置添加一個限定條件,用來規定此位置之前或者之后的字符必須滿足限定條件才能使正則中的字表達式匹配成功。

零寬斷言總共有四種

對於這個需求,實際上應該找到離聯系方式最近的一個<a,也就是說,在<a聯系方式之前不能再有其他的<a了。而最開始的正則匹配表達式<a.*?聯系方式.*?</a中的.*?可以通過.來匹配任意一個字符,在這里可以使用零寬度負先行斷言來限制.匹配的任何一個字符的右側不能夠再有<a。也就是.(?!<a),再將這個整體重復多次(.(?!<a))*?。這里引入了一個額外的括號,為了不產生多余的匹配,可以使用非捕獲組來去除不需要的匹配,最終可以將整個表達式寫成<a(:?.(?!<a))*?聯系方式.*?</a

可以看到匹配的范圍已經縮小到最后一個出現的超鏈接。

總結

因為正則表達式實現原理的限制,盡管選擇非貪婪匹配,匹配到的結果也不一定是最短的匹配。
通常正則表達式總是表明了“要匹配什么”,而通過零寬度負斷言,則可以表明“不匹配什么”,這比字符集中使用^來取反更加強大。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM