摘要:一正則表達(dá)式的工作機(jī)制畫(huà)了一個(gè)草圖,簡(jiǎn)單的說(shuō)明了下正則表達(dá)式的工作原理。只要正則表達(dá)式?jīng)]有嘗試完所有的可選項(xiàng),他就會(huì)回溯到最近的決策點(diǎn)也就是上次匹配成功的位置。而在正則表達(dá)中,主要就是之類(lèi)的數(shù)字引用。
本文同步自我的博客園:http://www.cnblogs.com/hustskyking/
關(guān)于正則表達(dá)式,網(wǎng)上可以搜到一大片文章,我之前也搜集了一些資料,并做了排版整理,可以看這篇文章http://www.cnblogs.com/hustskyking/archive/2013/06/04/RegExp.html,作為基礎(chǔ)入門(mén)講解,這篇文章說(shuō)的十分到位。
記得最開(kāi)始學(xué)習(xí)正則,是使用 php 做一個(gè)爬蟲(chóng)程序。為了獲取指定的信息,必須用一定的方式把有規(guī)律的數(shù)據(jù)匹配出來(lái),而正則是首選。下面是當(dāng)時(shí)寫(xiě)的爬蟲(chóng)程序的一個(gè)代碼片段:
$regdata = "/((?[^<]*)
){0,1}⊙(?.{12})S*s/"; //獲取頁(yè)面 $html = file_get_contents("http://www.qnwz.cn/html/daodu/201107/282277.html"); $html = iconv("GBK", "UTF-8", $html); if ($html == "") { die("
出錯(cuò):【錯(cuò)】無(wú)法打開(kāi)《青年文摘》頁(yè)面
"); } //匹配頁(yè)面信息 preg_match_all($regdata, $html, $mdata); print_r($mdata);
當(dāng)時(shí)寫(xiě)代碼還真是歡樂(lè)多,什么都不懂,什么都是新知識(shí),學(xué)起來(lái)津津有味。我覺(jué)得學(xué)習(xí)知識(shí)一定要把握最基本的原理,先把一個(gè)知識(shí)的大概輪廓搞清楚,然后學(xué)習(xí)怎么去使用他,完了就是深入學(xué)習(xí),了解底層基礎(chǔ)實(shí)現(xiàn)。很多人解決問(wèn)題都是靠經(jīng)驗(yàn),這個(gè)當(dāng)然很重要,但如果我們弄懂了一項(xiàng)技術(shù)最底層的實(shí)現(xiàn),完全可以靠自己的推斷分析出問(wèn)題的根源。我對(duì)一些公司的招聘要求特別不滿(mǎn),說(shuō)什么要三年五年Javascript編程經(jīng)驗(yàn)云云,經(jīng)驗(yàn)當(dāng)然和時(shí)間成正相關(guān),但是對(duì)于那些沒(méi)有三年五年工作經(jīng)驗(yàn)卻照樣能夠解決實(shí)際的人呢?算是小小的吐槽吧,下面進(jìn)入正題。
一、正則表達(dá)式的工作機(jī)制畫(huà)了一個(gè)草圖,簡(jiǎn)單的說(shuō)明了下正則表達(dá)式的工作原理。
+--------+ | 編譯 | +--------+ | ↓ +----------------+ | 設(shè)置開(kāi)始位置 |←---------+ +----------------+ ↑ | | ↓ 其 | +----------------+ 他 | | 匹配 & 回溯 | 路 | +----------------+ 徑 | | | ↓ | +----------------+ | | 成功 or 失敗 |---------→+ +----------------+
你寫(xiě)的任何一個(gè)正則直接量或者 RegExp 都會(huì)被瀏覽器編譯為一個(gè)原生代碼程序,第一次匹配是從頭個(gè)字符開(kāi)始,匹配成功時(shí),他會(huì)查看是否還有其他的路徑?jīng)]有匹配到,如果有的話(huà),回退到上一次成功匹配的位置,然后重復(fù)第二步操作,不過(guò)此時(shí)開(kāi)始匹配的位置(lastIndex)是上次成功位置加 1.這樣說(shuō)有點(diǎn)難以理解,下面寫(xiě)了一個(gè) demo,這個(gè) demo 就是實(shí)現(xiàn)一個(gè)正則表達(dá)式的解析引擎,因?yàn)檫壿嫼托Ч谋憩F(xiàn)都太復(fù)雜了,所以只做了一個(gè)簡(jiǎn)單的演示:
http://qianduannotes.duapp.com/demo/regexp/index.html
如果要深入了解正則表達(dá)式的內(nèi)部原理,必須先理解匹配過(guò)程的一個(gè)基礎(chǔ)環(huán)節(jié)——回溯,他是驅(qū)動(dòng)正則的一個(gè)基本動(dòng)力,也是性能消耗、計(jì)算消耗的根源。
二、回溯正則表達(dá)式中出現(xiàn)最多的是分支和量詞,上面的 demo 中可以很清楚的看到 hi 和 hello 這兩個(gè)分支,當(dāng)匹配到第一個(gè)字符 h 之后,進(jìn)入 (i|ello) 的分支選擇,首先是進(jìn)入 i 分支,當(dāng) i 分支匹配完了之后,再回到分支選擇的位置,重新選擇分支。簡(jiǎn)單點(diǎn)說(shuō),分支就是 | 操作符帶來(lái)的多項(xiàng)選擇問(wèn)題,而量詞指的是諸如 *, +?, {m,n} 之類(lèi)的符號(hào),正則表達(dá)式必須決定何時(shí)嘗試匹配更多的字符。下面結(jié)合回溯詳細(xì)說(shuō)說(shuō)分支和量詞。
1. 分支繼續(xù)分析上面那個(gè)案例。"Lalala. Hi, barret. Hello, John".match(/H(i|ello), barret/g),首先會(huì)查找 H 字符,在第九位找到 H 之后,正則子表達(dá)式提供了兩個(gè)選擇 (i|ello),程序會(huì)先拿到最左邊的那個(gè)分支,進(jìn)入分支后,在第十位匹配到了 i,接著匹配下一個(gè)字符,下一個(gè)字符是逗號(hào),接著剛才的位置又匹配到了這個(gè)逗號(hào),然后再匹配下一個(gè),依次類(lèi)推,直到完整匹配到整個(gè)正則的內(nèi)容,此時(shí)程序會(huì)在Hi, barret后面做一個(gè)標(biāo)記,表示在這里進(jìn)行了一次成功的匹配。但程序到此并沒(méi)有結(jié)束,因?yàn)楹竺婕恿艘粋€(gè)全局參數(shù),依然使用這個(gè)分支往后匹配,很顯然,到了 Hello 的時(shí)候,Hi 分支匹配不了了,于是程序會(huì)回溯到剛才我們做標(biāo)記的位置,并進(jìn)入第二個(gè)分支,從做標(biāo)記的位置重新開(kāi)始匹配,依次循環(huán)。
只要正則表達(dá)式?jīng)]有嘗試完所有的可選項(xiàng),他就會(huì)回溯到最近的決策點(diǎn)(也就是上次匹配成功的位置)。
2. 量詞量詞這個(gè)概念特別簡(jiǎn)單,只是在匹配過(guò)程中有貪婪匹配和懶惰匹配兩種模式,結(jié)合回溯的概念理解稍微復(fù)雜。還是用幾個(gè)例子來(lái)說(shuō)明。
1) 貪婪
str = "AB1111BA111BA"; reg = /AB[sS]+BA/; console.log(str.match(reg));
首先是匹配AB,遇到了 [sS]+,這是貪婪模式的匹配,他會(huì)一口吞掉后面所有的字符,也就是如果 reg 的內(nèi)容為 AB[sS]+,那后面的就不用看了,直接全部匹配,而往后看,正則后面還有B字符,所以他會(huì)先回溯到倒數(shù)第一個(gè)字符,匹配看是否為 B,顯然倒數(shù)第一個(gè)字符不是B,于是他又接著回溯,找到了B字母,找到之后就不繼續(xù)回溯了,而是往后繼續(xù)匹配,此刻匹配的是字符A,程序發(fā)現(xiàn)緊跟B后的字母確實(shí)是A,那此時(shí)匹配就結(jié)束了。如果沒(méi)有看明白,可以再讀讀下面這個(gè)圖:
REG: /AB[sS]+BA/ MATCH: A 匹配第一個(gè)字符 AB 匹配第二個(gè)字符 AB1111BA111BA [sS]+ 貪婪吞并所有字符 AB1111BA111BA 回溯,匹配字符B AB1111BA111B 找到字符B,繼續(xù)匹配A AB1111BA111BA 找到字符A,匹配完成,停止匹配
2) 懶惰(非貪婪)
str = "AB1111BA111BA"; reg = /AB[sS]+?BA/; console.log(str.match(reg));
與上面不同的是,reg 中多了一個(gè) ? 號(hào),此時(shí)的匹配模式為懶惰模式,也叫做非貪婪匹配。此時(shí)的匹配流程是,先匹配AB,遇到[sS]+?,程序嘗試跳過(guò)并開(kāi)始匹配后面的字符B,往后查看的時(shí)候,發(fā)現(xiàn)是數(shù)字1,不是要匹配的內(nèi)容,繼續(xù)往后匹配,知道遇到字符B,然后匹配A,發(fā)現(xiàn)緊接著B(niǎo)后面就有一個(gè)A,于是宣布匹配完成,停止程序。
REG: /AB[sS]+BA/ MATCH: A 匹配第一個(gè)字符 AB 匹配第二個(gè)字符 AB [sS]+? 非貪婪跳過(guò)并開(kāi)始匹配B AB1 不是B,回溯,繼續(xù)匹配 AB11 不是B,回溯,繼續(xù)匹配 AB111 不是B,回溯,繼續(xù)匹配 AB1111 不是B,回溯,繼續(xù)匹配 AB1111B 找到字符B,繼續(xù)匹配A AB1111BA 找到字符A,匹配完成,停止匹配
如果匹配的內(nèi)容是 AB1111BA,那貪婪和非貪婪方式的正則是等價(jià)的,但是內(nèi)部的匹配原理還是有區(qū)別的。為了高效運(yùn)用正則,必須搞清楚使用正則時(shí)會(huì)遇到那些性能消耗問(wèn)題。
三、逗比的程序//去測(cè)試下這句代碼 "TTTTTTTT".match(/(T+T+)+K/); //然后把前面的T重復(fù)次數(shù)改成30 //P.S:小心風(fēng)扇狂轉(zhuǎn),CPU暴漲
我們來(lái)分析下上面這段代碼,上面使用的都是貪婪模式,那么他會(huì)這樣做:
REG: (T+T+)+K MATCH: ①第一個(gè)T+匹配前7個(gè)T,第二個(gè)T+匹配最后一個(gè)T,沒(méi)找到K,宣布失敗,回溯到最開(kāi)始位置 ②第一個(gè)T+匹配前6個(gè)T,第二個(gè)T+匹配最后兩個(gè)T,沒(méi)找到K,宣布失敗,回溯到最開(kāi)始位置 ③... ... 接著還會(huì)考慮(T+T+)+后面的 + 號(hào),接著另一輪的嘗試。 ⑦... ...
這段程序并不會(huì)智能的去檢測(cè)字符串中是否存在 K,如果匹配失敗,他會(huì)選擇其他的匹配方式(路徑)去匹配,從而造成瘋狂的回溯和重新匹配,結(jié)果可想而知。這是回溯失控的典型例子。
四、前瞻和反向引用 1. 前瞻和引用前瞻有兩種,一種是負(fù)向前瞻,JS中使用 (?!xxx) 來(lái)表示,他的作用是對(duì)后面要匹配的內(nèi)容做一個(gè)預(yù)判斷,如果后面的內(nèi)容是xxx,則此段內(nèi)容匹配失敗,跳過(guò)去重新開(kāi)始匹配。另一種是正向前瞻,(?=xxx),匹配方式和上面相反,還有一個(gè)長(zhǎng)的類(lèi)似的是 (?:xxx),這個(gè)是匹配xxx,他是非捕獲性分組匹配,即匹配的內(nèi)容不會(huì)創(chuàng)建反向引用。具體內(nèi)容可以去文章開(kāi)頭提到的文檔中查看。
反向引用,這個(gè)在 replace 中用的比較多,在 replace 中:
字符 | 替換文本 |
---|---|
$1、$2、...、$99 | 與 regexp 中的第 1 到第 99 個(gè)子表達(dá)式相匹配的文本。 |
$& | 與 regexp 相匹配的子串。 |
$` | 位于匹配子串左側(cè)的文本。 |
$" | 位于匹配子串右側(cè)的文本。 |
$$ | 直接量符號(hào)。 |
而在正則表達(dá)中,主要就是 1, 2 之類(lèi)的數(shù)字引用。前瞻和反向引用使用恰當(dāng)可以大大的減少正則對(duì)資源的消耗。舉個(gè)例子來(lái)簡(jiǎn)單說(shuō)明下這幾個(gè)東西:
問(wèn)題:使用正則匹配過(guò)濾后綴名為 .css 和 .js 的文件。 如:test.wow.js test.wow.css test.js.js等等。
有人會(huì)立馬想到使用負(fù)向前瞻,即:
//過(guò)濾js文件 /(?!.+.js$).*/.exec("test.wow.js") //過(guò)濾js和css文件 /(?!.+.js$|.+.css$).*/.exec("test.wow.js") /(?!.+.js$|.+.css$).*/.exec("test.wow.html")
但是你自己去測(cè)試下,拿到的結(jié)果是什么。匹配非js和非css文件可以拿到正確的文件名,但是我們期望這個(gè)表達(dá)式對(duì)js和css文件的匹配結(jié)果是null,上面的表達(dá)式卻做不到。問(wèn)題是什么,因?yàn)??!xxx)和(?=xxx)都會(huì)消耗字符,在做預(yù)判斷的時(shí)候把 .js 和 .css 給消耗了,所以這里我們必須使用非捕獲模式。
/(?:(?!.+.js$|.+.css$).)*/.exec("test.wow.html"); /(?:(?!.+.js$|.+.css$).)*/.exec("test.wow.js");
我們來(lái)分析下這個(gè)正則:
(?:(?!.+.js$|.+.css$).)* --- ---------------- - | | | +----------------------+ ↓ | 非捕獲,內(nèi)部只有一個(gè)占位字符 | ↓ 負(fù)向前瞻以.js和.css結(jié)尾的字符串
最后一個(gè)星號(hào)是貪婪匹配,直接吞掉全部字符。
這里講的算是有點(diǎn)復(fù)雜了,不過(guò)在稍復(fù)雜的正則中,這些都是很基礎(chǔ)的東西了,想在這方面提高的童鞋可以多研究下。
2. 原子組JavaScript的正則算是比較弱的,他沒(méi)有分組命名、遞歸、原子組等功能特別強(qiáng)的匹配模式,不過(guò)我們可以利用一些組合方式達(dá)到自己的目的。上面的例子中,我們實(shí)際上用正則實(shí)現(xiàn)了一個(gè)或和與的功能,上面的例子體現(xiàn)的還不是特別明顯,再寫(xiě)個(gè)例子來(lái)展示下:
str1 = "我(wo)叫(jiao)李(li)靖(jing)"; str2 = "李(li)靖(jing)我(wo)叫(jiao)"; reg = /(?=.*?我)(?=.*?叫)(?=.*?李)(?=.*?靖)/; console.log(reg.test(str1)); //true console.log(reg.test(str2)); //true
不管怎么打亂順序,只要string中包含“我”,“是”,“李”,“靖”這四個(gè)字,結(jié)果都是true。
類(lèi)似(?=xxx)1,就相當(dāng)于一個(gè)原子組,原子組的作用就是消除回溯,只要是這種模式匹配過(guò)的地方,回溯時(shí)都不會(huì)到這里和他之前的地方。上面的程序"TTTTTTTT".match(/(T+T+)+K/);可以通過(guò)原子組的方式處理:
"TTTTTTTT".match(/(?=(T+T+))2+K/);
如此便能徹底消除回溯失控問(wèn)題。
五、小結(jié)關(guān)于正則的學(xué)習(xí),重點(diǎn)是要多練習(xí)多實(shí)踐,并且多嘗試用不同的方案去解決一個(gè)正則問(wèn)題,一個(gè)很典型的例子,去除字符串首尾的空白,嘗試用5-10種不同的正則去測(cè)試,并思考哪些方式的效率最高,為什么?通過(guò)這一連串的思考可以帶動(dòng)你學(xué)習(xí)的興趣,也會(huì)讓你成長(zhǎng)的比較快~
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/78024.html
摘要:元字符使正則表達(dá)式具有處理能力。所謂元字符就是指那些在正則表達(dá)式中具有特殊意義的專(zhuān)用字符,可以用來(lái)規(guī)定其前導(dǎo)字符即位于元字符前面的字符在目標(biāo)對(duì)象中的出現(xiàn)模式。 掌握...
摘要:正則大法好,正則大法好,正則大法好,重要的事情說(shuō)三遍。第二部分,這個(gè)部分是整個(gè)表達(dá)式的關(guān)鍵部分。學(xué)習(xí)正則如果還沒(méi)有系統(tǒng)學(xué)習(xí)正則表達(dá)式,這里提供一些網(wǎng)上經(jīng)典的教程供大家學(xué)習(xí)。正則表達(dá)式使用單個(gè)字符串來(lái)描述匹配一系列匹配某個(gè)句法規(guī)則的字符串。 原文收錄在我的 GitHub博客 (https://github.com/jawil/blog) ,喜歡的可以關(guān)注最新動(dòng)態(tài),大家一起多交流學(xué)習(xí),共同...
摘要:正則大法好,正則大法好,正則大法好,重要的事情說(shuō)三遍。第二部分,這個(gè)部分是整個(gè)表達(dá)式的關(guān)鍵部分。學(xué)習(xí)正則如果還沒(méi)有系統(tǒng)學(xué)習(xí)正則表達(dá)式,這里提供一些網(wǎng)上經(jīng)典的教程供大家學(xué)習(xí)。正則表達(dá)式使用單個(gè)字符串來(lái)描述匹配一系列匹配某個(gè)句法規(guī)則的字符串。 原文收錄在我的 GitHub博客 (https://github.com/jawil/blog) ,喜歡的可以關(guān)注最新動(dòng)態(tài),大家一起多交流學(xué)習(xí),共同...
摘要:非貪婪匹配默認(rèn)情況下,正則表達(dá)式的量詞,都是進(jìn)行貪婪匹配,即匹配盡可能多的字符。參考正則表達(dá)式關(guān)于專(zhuān)注于微信小程序微信小游戲支付寶小程序和線(xiàn)上應(yīng)用實(shí)時(shí)監(jiān)控。自從年雙十一正式上線(xiàn),累計(jì)處理了億錯(cuò)誤事件,付費(fèi)客戶(hù)有金山軟件百姓網(wǎng)等眾多品牌企業(yè)。 摘要:正則表達(dá)式是程序員的必備技能,想不想多學(xué)幾招呢? showImg(https://segmentfault.com/img/bV9L9n?w...
閱讀 3332·2023-04-26 00:07
閱讀 3939·2021-11-23 10:08
閱讀 2947·2021-11-22 09:34
閱讀 861·2021-09-22 15:27
閱讀 1754·2019-08-30 15:54
閱讀 3753·2019-08-30 14:07
閱讀 922·2019-08-30 11:12
閱讀 686·2019-08-29 18:44