一、基本概念:
在我們寫頁(yè)面時(shí),常需要對(duì)表單數(shù)據(jù)如賬號(hào)、密碼、身份證號(hào)等進(jìn)行驗(yàn)證,而最有效、用的最多的就是使用正則表達(dá)式來(lái)驗(yàn)證。
正則表達(dá)式(Regular Expression)是用于描述一組字符串特征的模式,用于匹配特定的字符串。其應(yīng)用非常廣泛,特別是在字符串處理方面,應(yīng)用如下:
1、驗(yàn)證字符串,即驗(yàn)證給定的字符串或子字符串是否符合指定的特征,可驗(yàn)證郵箱地址等。
2、查找字符串,從給定的文本中查找符號(hào)指定特征的字符串,比查找固定字符串更靈活。
3、替換字符串,即查找到符合某特征的字符串后將其替換。
4、提取字符串,即從給定的字符串中提取出符合指定特征的子字符串。
二、檢測(cè)工具介紹
介紹一個(gè)工具RegexBuddy

三、結(jié)構(gòu)總體介紹
1、字符字面量:
匹配一個(gè)具體字符,包括不用轉(zhuǎn)義的和需要轉(zhuǎn)義的。比如a匹配字符"a",又比如\n匹配換行符,又比如\.匹配小數(shù)點(diǎn)。
2、字符組:
匹配一個(gè)字符,可以是多種可能之一,比如[0-9],表示匹配一個(gè)數(shù)字。也有\d的簡(jiǎn)寫形式。另外還有反義字符組,表示可以是除了特定字符之外任何一個(gè)字符,比如[^0-9],表示一個(gè)非數(shù)字字符,也有\D的簡(jiǎn)寫形式。
3、量詞:
表示一個(gè)字符連續(xù)出現(xiàn),比如a{1,3}表示“a”字符連續(xù)出現(xiàn)1到3次。另外還有常見的簡(jiǎn)寫形式,比如a+表示“a”字符連續(xù)出現(xiàn)至少一次。
4、錨點(diǎn):
匹配一個(gè)位置,而不是字符。比如^匹配字符串的開頭,又比如\b匹配單詞邊界,又比如(?=\d)表示數(shù)字前面的位置。
5、分組:
用括號(hào)表示一個(gè)整體,比如(ab)+,表示"ab"兩個(gè)字符連續(xù)出現(xiàn)多次,也可以使用非捕獲分組(?:ab)+。
6、分支:
多個(gè)子表達(dá)式多選一,比如abc|bcd,表達(dá)式匹配"abc"或者"bcd"字符子串。
7、反向引用:
比如\2,表示引用第2個(gè)分組。
四、結(jié)構(gòu)詳細(xì)介紹
4.1 字符組
雖然是叫字符組,但只是表示其中一個(gè)字符,例如[abc],表示匹配一個(gè)字符,它可以是“a”、“b”、“c”之一。
(1)范圍表示法:
比如[123456abcdefGHIJKLM],可以寫成[1-6a-fG-M]。用連字符-來(lái)省略和簡(jiǎn)寫。
要匹配“a”、“-”、“z”這三者中任意一個(gè)字符,該怎么做呢?不能寫成[a-z],因?yàn)槠浔硎拘懽址械娜魏我粋€(gè)字符??梢詫懗扇缦碌姆绞剑?b>[-az]或[az-]或[a\-z]。即要么放在開頭,要么放在結(jié)尾,要么轉(zhuǎn)義??傊粫?huì)讓引擎認(rèn)為是范圍表示法就行。
(2)排除字符組:
某位字符可以是任何東西,但就不能是"a"、"b"、"c",排除字符組是反義字符組的概念。
例如[^abc],表示是一個(gè)除"a"、"b"、"c"之外的任意一個(gè)字符。字符組的第一位放^(脫字符),表示求反的概念。
(3)常見的簡(jiǎn)寫形式:
\d就是[0-9]。表示是一位數(shù)字。記憶方式:其英文是digit(數(shù)字)。
\D就是[^0-9]。表示除數(shù)字外的任意字符。
\w就是[0-9a-zA-Z_]。表示數(shù)字、大小寫字母和下劃線。記憶方式:w是word的簡(jiǎn)寫,也稱單詞字符。
\W是[^0-9a-zA-Z_]。非單詞字符。
\s是[ \t\v\n\r\f]。表示空白符,包括空格、水平制表符、垂直制表符、換行符、回車符、換頁(yè)符。記憶方式:s是space character的首字母。
\S是[^ \t\v\n\r\f]。 非空白符。
.就是[^\n\r\u2028\u2029]。通配符,表示幾乎任意字符。換行符、回車符、行分隔符和段分隔符除外。記憶方式:想想省略號(hào)...中的每個(gè)點(diǎn),都可以理解成占位符,表示任何類似的東西。
如果要匹配任意字符怎么辦?可以使用[\d\D]、[\w\W]、[\s\S]和[^]中任何的一個(gè)。
4.2 量詞
量詞也稱為重復(fù),表示重復(fù)多少次;例如:\d{3}表示連續(xù)匹配數(shù)字3次,即匹配三個(gè)連貫的數(shù)字。其中{}是閉區(qū)間。
(1)簡(jiǎn)寫形式
{m,} 表示至少出現(xiàn)m次。
{m} 等價(jià)于{m,m},表示出現(xiàn)m次。
? 等價(jià)于{0,1},表示出現(xiàn)或者不出現(xiàn)。記憶方式:?jiǎn)柼?hào)表示,有或沒有
+ 等價(jià)于{1,},表示出現(xiàn)至少一次。
* 等價(jià)于{0,},表示出現(xiàn)任意次,有可能不出現(xiàn)。
(2)量詞中存在的貪婪匹配和惰性匹配
例如:

其中正則\d{2,5},表示數(shù)字連續(xù)出現(xiàn)2到5次。會(huì)匹配2位、3位、4位、5位連續(xù)數(shù)字。但是其是貪婪的,它會(huì)盡可能多的匹配。你能給我6個(gè),我就要5個(gè)。你能給我3個(gè),我就要3個(gè)。反正只要在能力范圍內(nèi),越多越好。
而惰性匹配,就是盡可能少的匹配;

其中\d{2,5}?表示,雖然2到5次都行,當(dāng)2個(gè)就夠的時(shí)候,就不在往下嘗試了。
4.3 錨點(diǎn)
錨點(diǎn)即位置匹配,介紹錨點(diǎn)之前先了解下位置的概念。
(1)什么是位置
位置是相鄰字符之間的位置。

(2)如何匹配位置
有8個(gè)錨字符可以去匹配位置
^? ? $? ? \b? ? \B? ? (?=p)? ? (?!p)??(?<=p) (?<!p)
其中
^和$
^(脫字符)匹配開頭,在多行匹配中匹配行開頭。$(美元符號(hào))匹配結(jié)尾,在多行匹配中匹配行結(jié)尾。比如,把字符串的開頭和結(jié)尾用“#”替換。

\b和\B
\b是單詞邊界,具體就是\w和\W之間的位置,也包括\w和^之間的位置,也包括\w和$之間的位置。
例如,將指定字符的邊界替換為#

\B就是\b的反面的意思,非單詞邊界。例如在字符串中所有位置中,扣掉\b,剩下的都是\B的。

(?=p) 和 (?!p)
(?=p),學(xué)名叫零寬度正預(yù)測(cè)先行斷言,其中p是一個(gè)子模式,即p前面的位置。例如(?=l),表示'l'字符前面的位置

而(?!p),學(xué)名叫零寬度負(fù)預(yù)測(cè)先行斷言,就是(?=p)的反面意思,比如:

(3)案例分析
數(shù)字的千位分隔符表示法,如把“12345678”,變成“12,345,678”。
思路:找到對(duì)應(yīng)的位置替換為“,”。
1、先弄出最后一個(gè)逗號(hào)

2、弄出所有逗號(hào)
因?yàn)槎禾?hào)出現(xiàn)的位置,要求后面3個(gè)數(shù)字一組,也就是\d{3}至少出現(xiàn)一次。此時(shí)可以使用量詞+:

3、要求匹配的位置不能是開頭


(?<=p) 和(?<!p)
注:這兩種模式j(luò)avascript是不支持的,請(qǐng)?jiān)赗egexBuddy工具中把語(yǔ)言切換到c#模式。
(?<=p) ,學(xué)名叫零寬度正回顧后發(fā)斷言,表示p后面的這個(gè)位置

(?<!p),學(xué)名叫零寬度負(fù)回顧后發(fā)斷言,表示不是p后面的位置,或者說(shuō)是p后面的位置不能匹配;

4.4 多選分支
形式如:(p1|p2|p3),其中p1、p2、p3是子模式,用|(管道符)分隔,表示其中任何之一。
它也是惰性匹配,即找到一個(gè)匹配的就不繼續(xù)嘗試查找了。
4.5 括號(hào)的作用
a+匹配連續(xù)出現(xiàn)的“a”,而要匹配連續(xù)出現(xiàn)的“ab”時(shí),需要使用(ab)+

多選分支結(jié)構(gòu)(p1|p2)中,括號(hào)提供了子表達(dá)式的所有可能。如:

反向引用
在正則里引用之前出現(xiàn)的分組,即反向引用。如:當(dāng)我們想用正則支持如下三種日期格式時(shí):

以上正則雖然能匹配要求的情況,但也匹配了“2019-09/10”這樣的數(shù)據(jù),如果想要分割符前后一致,需要使用反向引用;

其中,\1表示引用之前的那個(gè)分組(-|\/|\.)。不管它匹配到什么(比如-),\1都匹配那個(gè)同樣的具體某個(gè)字符。我們知道了\1的含義后,那么\2和\3的概念也就理解了,即分別指代第二個(gè)和第三個(gè)分組。
當(dāng)括號(hào)存在嵌套時(shí),以左括號(hào)為準(zhǔn),如

我們可以看看這個(gè)正則匹配模式:
第一個(gè)字符是數(shù)字,比如說(shuō)1,
第二個(gè)字符是數(shù)字,比如說(shuō)2,
第三個(gè)字符是數(shù)字,比如說(shuō)3,
接下來(lái)的是\1,是第一個(gè)分組內(nèi)容,那么看第一個(gè)開括號(hào)對(duì)應(yīng)的分組是什么,是123,
接下來(lái)的是\2,找到第2個(gè)開括號(hào),對(duì)應(yīng)的分組,匹配的內(nèi)容是1,
接下來(lái)的是\3,找到第3個(gè)開括號(hào),對(duì)應(yīng)的分組,匹配的內(nèi)容是23,
最后的是\4,找到第4個(gè)開括號(hào),對(duì)應(yīng)的分組,匹配的內(nèi)容是3。
引用不存在的分組
反向引用,是引用前面的分組,但我們在正則里引用了不存在的分組時(shí),有些語(yǔ)言會(huì)報(bào)編譯錯(cuò)誤(引用了不存在的分組),如c#,但有些語(yǔ)言是支持的,如javascript,只是匹配反向引用的字符本身。例如\2,就匹配"\2"。注意"\2"表示對(duì)"2"進(jìn)行了轉(zhuǎn)意。大概如:

非捕獲分組
之前文中出現(xiàn)的分組,都會(huì)捕獲它們匹配到的數(shù)據(jù),以便后續(xù)引用,因此也稱他們是捕獲型分組。如果只要括號(hào)最原始的功能,但不會(huì)引用它,即,既不在API里引用,也不在正則里反向引用。此時(shí)可以使用非捕獲分組(?:p),注意和上文中的(?=p)進(jìn)行區(qū)分。

4.6 回溯法原理
引擎會(huì)依次處理各個(gè)子表達(dá)式或組成元素,遇到需要在兩個(gè)可能成功的選項(xiàng)進(jìn)行選擇的時(shí)候,它會(huì)選擇其一,同時(shí)記住另一個(gè),以備稍后可能的需要。如果正則表達(dá)式中余下的部分最終匹配失敗,引擎會(huì)知道需要回溯到之前做出選擇的地方,選擇其他的備用分支繼續(xù)嘗試。這樣,引擎最終會(huì)嘗試表達(dá)式的所有可能途徑(或者是匹配完成之前需要的所有途徑)。
形象的說(shuō),回溯就像是在道路的每個(gè)分岔口留下一小堆面包屑。如果走了死路,就可以照原路返回,直到遇到面包屑標(biāo)示的尚未嘗試過的道路。如果那條路也走不通,你可以繼續(xù)返回,找到下一堆面包屑,如此重復(fù),直到找到出路,或者走完所有沒有嘗試過的路。
(1)沒有回溯的匹配
假設(shè)我們的正則是ab{1,3}c,其可視化形式是

當(dāng)目標(biāo)字符串是"abbbc"時(shí),就沒有所謂的“回溯”。其匹配過程是

(2)有回溯的匹配
如果目標(biāo)字符串是“abbc”,則會(huì)存在回溯。

圖中第5步有紅顏色,表示匹配不成功。此時(shí)b{1,3}已經(jīng)匹配到了2個(gè)字符“b”,準(zhǔn)備嘗試第三個(gè)時(shí),結(jié)果發(fā)現(xiàn)接下來(lái)的字符是“c”。那么就認(rèn)為b{1,3}就已經(jīng)匹配完畢。然后狀態(tài)又回到之前的狀態(tài)(即第6步,與第4步一樣),最后再用子表達(dá)式c,去匹配字符“c”。當(dāng)然,此時(shí)整個(gè)表達(dá)式匹配成功了。其中的第6步就是回溯。
再舉個(gè)例子,正則是ab{1,3}bbc

目標(biāo)字符串是"abbbc",匹配過程是:

其中第7步和第10步是回溯。第7步與第4步一樣,此時(shí)b{1,3}匹配了兩個(gè)"b",而第10步與第3步一樣,此時(shí)b{1,3}只匹配了一個(gè)"b",這也是b{1,3}的最終匹配結(jié)果。
(3)回溯法總結(jié)
回溯法也稱試探法,它的基本思想是:從問題的某一種狀態(tài)(初始狀態(tài))出發(fā),搜索從這種狀態(tài)出發(fā)所能達(dá)到的所有“狀態(tài)”,當(dāng)一條路走到“盡頭”的時(shí)候(不能再前進(jìn)),再后退一步或若干
步,從另一種可能“狀態(tài)”出發(fā),繼續(xù)搜索,直到所有的“路徑”狀態(tài)都嘗試過。這種不斷“前進(jìn)”、不斷“回溯”尋找解的方法,就稱為“回溯法”。
(4)可能出現(xiàn)回溯的地方
貪婪量詞
之前的例子都是貪婪量詞相關(guān)的。比如b{1,3},因?yàn)槠涫秦澙返模瑖L試可能的順序是從多往少的方向去嘗試。首先會(huì)嘗試"bbb",然后再看整個(gè)正則是否能匹配。不能匹配時(shí),吐出一個(gè)"b",即在"bb"的基礎(chǔ)上,再繼續(xù)嘗試。如果還不行,再吐出一個(gè),再試。如果還不行呢?只能說(shuō)明匹配失敗了。
惰性量詞
惰性量詞就是在貪婪量詞后面加個(gè)問號(hào),表示盡可能少的匹配。雖然惰性量詞不貪,但也會(huì)有回溯的現(xiàn)象。比如正則是


雖然惰性匹配不貪,但是為了整體匹配成,也只能給你多塞點(diǎn)。因此最后\d{1,3}?匹配的字符是"12",是兩個(gè)數(shù)字,而不是一個(gè)。
五、實(shí)踐
(1)試著用正則表達(dá)式匹配IP地址;
(2)驗(yàn)證用戶密碼,要求必須是大、小寫字母、數(shù)字至少兩種的結(jié)合;
(3)了解下正則表達(dá)式引發(fā)的血案,你知道問題出在哪兒?jiǎn)幔? ?https://juejin.im/post/5b287ea6f265da596d04a324