摘要:注意本文將正則與中的正則分開討論。正則零寬斷言更多參考各種語言對于正則不同支持參考單行模式與多行模式通過設置正則表達式后的修飾符可開啟對應的匹配模式單行模式和多行模式。
最近這段時間幫同學處理一些文檔, 涉及到一些結構化文檔的工作大部分都得使用正則表達式, 之前對于正則的認識大多來源于語言書上那幾頁的介紹, 自己也沒有用過幾次。這里將我之前感到模糊的概念作個整理。因為對JS了解多點,所以也將JS中相關的正則特性歸納下。注意本文將正則與JS中的正則分開討論。
正則引擎正則表達式的解釋引擎只有兩種,字符驅動(text-directed)和正則驅動(regex-directed),基于這兩種解釋引擎算法的不同,它們有時也被稱為DFA(Deterministic finite automaton 確定型有窮自動機)與NFA(Non-deterministic finite automaton非確定型有窮自動機),當前大部分現代語言的正則解釋器采用“正則驅動”引擎,這是因為NFA運行的回溯算法可以實現諸如‘惰性匹配(lazy quantifiers )’和‘后部引用(backreferences)’ 等非常有用的特性, 而DFA算法也就是字符驅動型引擎并不支持這些特性,當然復雜的算法必須付出性能的代價,字符驅動引擎的性能要強于正則驅動。各種語言中由于實現正則引擎的具體算法以及調用的正則庫都有不同,所以對于正則的支持也不同,關于這些概念的參考mark在這里:
?Regex Engine Internals
?正則表達式匹配解析過程探討分析(正則表達式匹配原理)
零寬斷言(zero-length assertions)這概念直譯過于術語化(都有點像咒語了,囧rz), 邏輯不是太復雜,但是容易混淆模糊,在此記個筆記。零寬的意思是指該位置是不占寬度的,也就是只作斷言判斷,但不匹配實際的內容,比如d(?=.)這個正向先行斷言(這概念也歸納在后)就只匹配點號之前的數字,但是它并不會匹配到這個點號,這個d(?=.)括號中的匹配內容也就是零寬了。
零寬斷言分為四種,分別是:正向先行(Positive Lookahead),正向回顧(Positive Lookbehind),負向先行(Negative Lookahead),負向回顧(Negative Lookbehind)。正向和負向的意思是斷言括號中的內容是匹配還是不匹配,比如上面栗子?中的(?=...)就是正向先行斷言的寫法,該斷言括號中的內容必須出現, 而負向也就是斷言括號中的內容不能出現,負向先行的寫法是這樣:(?!...)。 先行與回顧的意思是實際匹配的內容在斷言內容的前面還是后面,以下將逐一羅列這四種概念:
直接地說“零寬正向先行斷言”所匹配的就是必須出現的斷言內容的前面的內容,之前的點號前數字的栗子?d(?=.)已經比較直觀的展示了這個概念,但是需要注意的是:這里的先行或者回顧是基于位置,而不是字符!也就是說從點號的位置開始向前匹配, 比如我們把這個正則表達式改成這樣:(?=.).注意這里并沒有把實際要匹配的內容放在斷言括號的前面,而是放到了后面, 這就匹配了從這個點號位置開始的任意字符(除換行符外),也就匹配到了這個點號, 當然通常這樣寫沒什么意義, 但便于理解位置的含義。
正向回顧理解了前面這個“零寬正向先行斷言”,隨之而來的就比較好理解了。依照這個邏輯,零寬正向回顧斷言所匹配的是必須出現的斷言內容之后的內容,它的語法是:(?<=...) 比如(?<=.)w所匹配的就是點號之后的ASCII字符。
負向先行負向與正向意思相反, 正向是斷言內容必須出現,而負向則是斷言內容必須不出現。比如JavaScript and Java這句話中使用Java(?!Script)就只會匹配到Java,因為該正則斷言Java后面不能出現Script
負向回顧正負向與先行回顧的概念都已在前面列出, 負向回顧的理解應該就很順了。負向回顧的語法是:(?。 比如(?該正則只匹配不是JavaScript的Script,它就能正確匹配ECMAScript和Script。
這里需要注意:在大多數語言中,先行斷言中可以有任意的正則語法,比如可以用+或者*這種不確定重復的匹配,但是回顧斷言中的限制就比較多,很多語言的回顧斷言中不支持諸如+和*這種不定重復的匹配和后部引用,因為正則解釋引擎在匹配回顧斷言的時候,當回顧斷言中的內容匹配成功后再去匹配斷言后的實際內容時,它需要能計算出后退多少步才能判斷出當前匹配是否與這個正則完全匹配。有些語言壓根就不支持回顧斷言,比如JS;而 PHP, Delphi, R和 Ruby只支持在回顧斷言中加入選擇匹配,但選擇匹配的內容長度必須相同,形如[ab|ba|cd];Java回顧斷言限制不多,但無法在回顧斷言中加入不定數量的重復,也就是不能在回顧斷言中使用+和*; .Net支持在回顧斷言中使用任意正則語法。(以上內容參考自下面的第一個mark參考中,可能存在時效的問題, 如有錯誤還請大神指出。)
正則零寬斷言更多參考mark:
?Lookahead and Lookbehind Zero-Length Assertions
各種語言對于正則不同支持參考:
?Comparison of regular expression engines
通過設置正則表達式后的修飾符(flag)可開啟對應的匹配模式:s(單行模式)和m(多行模式)。單行模式與多行模式這兩名字容易讓人誤以為兩者是互相排斥的, 也就是誤以為開啟了多行模式,那么就不是單行模式,而實際上單行模式或多行模式的開關并不會影響另一個,兩個模式可以同時用。
單行模式開關單行模式影響的是元字符.的匹配。單行模式開啟時,元字符.匹配包括換行符 在內的任意字符,單行模式關閉時,元字符.匹配不包括換行符 的任意字符。
多行模式多行模式影響的是元字符^和$。多行模式開啟時,元字符^可以匹配字符串開頭(字符串的開始位置),也可以匹配行的開頭(即換行符
之后的位置),元字符$ 可以匹配字符串結尾(字符串的結束位置), 也可以匹配行的結尾(即換行符
之前的位置)。多行模式關閉時,元字符^和$只能匹配字符串的開頭和結尾,不能匹配換行符
的之前和之后。也就是說如果沒有這兩個元字符,即使正則后加多行模式修飾符m也無意義,多行模式的正則必須有這兩個元字符中的一個或兩個才有意義。
概念參考mark: ?正則表達式的多行模式與單行模式
附一個JS描述的多行模式:?multiline 屬性(正則表達式)(JavaScript)
JS中的正則注意:JS中正則并不支持單行模式的開啟
JS中的正則需要注意下正則對象的方法與String對象方法的微妙區別。
RegExp對象每個正則對象實例有這幾項屬性global(是否帶有修飾符g的布爾值) ignoreCase(是否帶有修飾符i的布爾值) lastIndex(對象方法匹配開始的位置索引,初始為0) multiline(是否帶有修飾符m的布爾值) source(正則源碼文本,注意源碼文本不包括修飾符)。
ES6中RegExp對象實例新增的只讀屬性sticky可以判斷正則后是否帶修飾符y(新增),設置y修飾符的正則每次匹配,存在lastIndex屬性情況下lastIndex不會自動歸零,并且在正則表達式中隱式地加入了開頭元字符^,這樣就使得正則表達式的匹配錨定在lastIndex的位置。這種用法的詳情可參考MDN文檔的栗子?:?RegExp.prototype.sticky 以及?JavaScript:正則表達式的/y標識
JS中每個RegExp對象實例有兩個方法,分別是:exec()與test()。這兩個方法運行邏輯幾乎等價,但是exec要復雜些,匹配失敗返回null,匹配成功它將返回一個數組, 數組第一個元素是匹配整個正則的內容,之后的元素是正則中捕獲組的匹配(注:使用非捕獲組可以獲得微弱的性能優勢)舉個栗子:
//捕獲組() var pattern1 = /(www).(baidu).(com)/i //非捕獲組(?:) var pattern2 = /(?:www).(?:baidu).(?:com)/i var str = "www.baidu.com" pattern1.exec(str) //返回數組["www.baidu.com", "www", "baidu", "com"] pattern2.exec(str) //返回數組["www.baidu.com"]
注意:雖然exec()和String的match()方法返回的都是數組的實例,但是他們的返回值都有兩個額外的屬性:index和input分別表示匹配項在字符串中的位置和應用正則的整段字符串。
而test()方法更為簡單,匹配成功返回true,失敗返回false。這里需要注意下這兩個方法中的lastIndex屬性與正則表達式中的全局修飾符g的關系。
當正則中存在全局修飾符g時,RegExp對象的lastIndex屬性將保存下一次開始的匹配的位置,比如:
var pattern = /(www).(baidu).(com)/g var str2 = "www.baidu.com www.google.com www.baidu.com" pattern.exec(str2) pattern.lastIndex //13
這意味著下次調用exec方法它將從被匹配字符串索引13的位置開始匹配(也可以手動設置lastIndex的值)。test()方法同理。正因為存在殘留的lastIndex,所以使用正則對象的方法可能會導致一些意外的結果,不過在ES5中,正則表達式直接量的每次計算都會創建一個RegExp對象實例,他們各自擁有lastIndex,這就大大降低了意外的概率。
但是String對象中支持正則的方法卻與正則對象的方法不同,String方法忽略lastIndex,但是,如果存在全局修飾符g,string方法將有些許不同。
String方法中支持正則的有match(),replace(), search(),split()這四個方法。
match()方法接受一個字符串或正則作為參數,當參數是正則時, 正則是否帶全局修飾符g將影響該方法的返回值,該方法匹配不帶全局修飾符的正則時,與RegExp對象實例方法exec()的返回值一樣, 匹配帶全局修飾符的正則時,match()方法將返回全局中所匹配到的所有內容而不再將正則中的捕獲組編碼。比如前面的str2如果使用String的match方法的話其返回的數組中將是["www.baidu.com","www.baidu.com"]。
replace(searchValue,replaceValue)方法同樣接受字符串或正則作為第一個參數,當搜索值是字符串的時候,它只會匹配一次,很多時候這并非我們要的結果, 一般我們希望全局匹配,于是可以使用帶全局修飾符的正則表達式。replace()的替換值中的美元符號$有特殊含義,比如$1表示正則表達式中第一個捕獲組的匹配內容。它的意義取自RegExp構造函數的屬性(正則表達式每次的操作都會影響構造函數RegExp的屬性,該構造函數屬性的詳細參考可以看下《JS高級程序設計》中文第三版108頁中的說明)下面借用《JS權威指南》核心參考中的栗子。
var name = "Doe,John" name.replace(/(w+)*,(w+)/,"$2 $1")//$num代表捕獲組的編號 "John Doe"
美元符號的特殊含義歸納:
$$ 替換對象$
$& 整個匹配的文本
$number 分組捕獲的文本(從1開始,不是0哦)
$` 匹配之前的文本
$" 匹配之后的文本
search()方法與String中indexOf類似,接受一個正則或者字符串為參數,匹配成功返回第一個匹配的首字符位置, 失敗則返回-1。
split(separator,howmany)方法接受一個必選的分割位置作為第一個參數,第二個參數設置返回數組的最大長度。第一個參數可以是字符串或正則表達式,如果該參數是包含捕獲組(帶圓括號帶子表達式)的正則表達式,那么返回的數組中包括與這些子表達式匹配的字串(但不包括與整個正則表達式匹配的文本)
舉個例子?:
"hello world".split(/(w)s/) //返回["hell", "o", "world"]
可以看到捕獲組中的匹配("o")也在返回的數組中,但是整個正則的匹配被作為分隔位置。并非所有瀏覽器都支持split()這個特性。
MDN文檔中并未說明該特性更多細節:?String.prototype.split()
這里也順便轉貼一下《JS高級程序設計》第三版中提到的JS(ES5)正則的局限性:
匹配字符串開始的結尾的A和Z錨(但支持以^和$來匹配字符串的開始的結尾)
向后查找(但支持向前查找)
并集和交集類
原子組
Unicode支持(單個字符除外)
命名的捕獲組(但支持編號的捕獲組)
s(single單行)和x(free-spacing無間隔)匹配模式
條件匹配
正則表達式注釋
JS的正則庫以上所提到的局限性中,可使用正則庫或多或少的抵消一些,比如XRegExp 調用該庫之后就可以使JS的正則支持可命名空間的捕獲組以及注釋等有用的特性, 關于JS的正則庫XRegExp,我寫了篇大致的介紹:?JavaScript正則庫:XRegExp
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78274.html
摘要:非貪婪模式盡可能少的匹配所搜索的字符串,而默認的貪婪模式則盡可能多的匹配所搜索的字符串。 導讀 你有沒有在搜索文本的時候絞盡腦汁, 試了一個又一個表達式, 還是不行. 你有沒有在表單驗證的時候, 只是做做樣子(只要不為空就好), 然后燒香拜佛, 虔誠祈禱, 千萬不要出錯. 你有沒有在使用sed 和 grep 命令的時候, 感覺莫名其妙, 明明應該支持的元字符, 卻就是匹配不到. 甚至,...
摘要:表示非單詞字符,等效于正則教程返回完整的字符串,因為,中文算作是非單詞字符。行首行尾,修飾符形式修飾符的作用是修改和在正則表達式中的作用,讓它們分別表示行首和行尾。 正則 描述 正則 描述 f 匹配換頁符 匹配制表符 匹配換行符 v 匹配垂直制表符 匹配回車 s 匹配單個空格,等同于[f v]; S...
摘要:正則表達式一直是里比較難以掌握的點。在中創建正則的兩種方式使用字面量這就是正則表達式的字面量語法,表示正則表達式的模式,為正則表達式的標志。字面量形式的正則表達式一般使用較多,也推薦大家盡可能使用這種形式,簡潔易讀,符合正常的使用習慣。 正則表達式一直是js里比較難以掌握的點。 看不懂,學不會,記不住。 每次需要用到正則的時候,都需要再去查找資料。 今天花時間把正則的知識點總結下,希望...
閱讀 2112·2023-04-26 00:41
閱讀 1142·2021-09-24 10:34
閱讀 3573·2021-09-23 11:21
閱讀 4030·2021-09-22 15:06
閱讀 1557·2019-08-30 15:55
閱讀 896·2019-08-30 15:54
閱讀 1828·2019-08-30 15:48
閱讀 549·2019-08-29 13:58