構(gòu)建掃描器程序的兩個(gè)重要方法
boolean hitEnd()
- 返回true,說明結(jié)尾有更多的字符可能會(huì)改變本次的匹配結(jié)果(匹配成功變成失敗,失敗變成成功,或者匹配內(nèi)容發(fā)生改變)
- 返回false,說明結(jié)尾有更多的字符不會(huì)改變本次的匹配結(jié)果
boolean requireEnd()
只有匹配成功該方法的返回值才有意義
- 返回true,說明結(jié)尾有更多的字符可能會(huì)使得本次匹配失敗
- 返回false,說明結(jié)尾有更多的字符不可能會(huì)使得本次匹配失敗,雖然可能會(huì)影響本次匹配的細(xì)節(jié),如匹配內(nèi)容發(fā)生改變
消除循環(huán)
(normal)*(special(normal)*)*
- 防止special部分造成的循環(huán)
special
要能寫成start(middle)*end
-
start-end
和middle
匹配的是無交集的兩部分 -
start
和end
是固化的文本,且start
和end
至少存在一部分,middle
可不存在
- start(middle)* 是為了防止此模式匹配成功后,外層(...)*表達(dá)式還能夠繼續(xù)匹配,導(dǎo)致內(nèi)部和外部遞歸匹配一段文本,加上start,當(dāng)middle回溯文本時(shí),外部無法再匹配內(nèi)部交還的文本
- (middle)*end 是為了防止本次的匹配成功,如果本次不能匹配成功,那么外部也無法繼續(xù)應(yīng)用該表達(dá)式了,
- 防止normal部分造成的循環(huán)
- 把start看成special,middle看成normal,也就保證了(special(normal))不會(huì)造成無休止的匹配了,這是(start(middle)end)缺少end的一種形式
表達(dá)式的優(yōu)化
對(duì)于
(.*)(Suffix)
這種以.*
匹配優(yōu)先開始的表達(dá)式通常可以在開頭使用行開頭錨點(diǎn)標(biāo)記^
,
得到^(.*)(Suffix)
這樣傳動(dòng)裝置只會(huì)在文本開頭應(yīng)用一次該表達(dá)式,因?yàn)槠ヅ鋬?yōu)先總是能夠匹配完全部的文本,當(dāng)
(Suffix)
匹配時(shí),會(huì)強(qiáng)迫之前的表達(dá)式歸還文本,所以如果能夠匹配成功,(Suffix)
匹配的總是文本中最后一個(gè)滿足的匹配,以后的文本不存在能夠與(Suffix)
相匹配的了,所以后面的嘗試都是多余的,而使用^
行錨點(diǎn)標(biāo)記,傳動(dòng)裝置將不會(huì)做多余的嘗試.
- 將結(jié)尾部分分散到多選結(jié)構(gòu)內(nèi),使用
(?:com\b|edu\b|org\b|int\b|net\b|biz\b|coop\b|areo\b)
而不使用(?:com|edu|org|int|net|biz|coop|areo)\b
,后者每當(dāng)一個(gè)多選分支匹配成功后,將退出多選分支,但是在\b
將失敗,此時(shí)又會(huì)回溯,如果將\b
添加在多選結(jié)構(gòu)內(nèi),則在未退出多選結(jié)構(gòu)就能發(fā)現(xiàn)匹配失敗,這個(gè)優(yōu)化是有風(fēng)險(xiǎn)的,如果表達(dá)式是(?:this|that):
則改(?:this:|that:)
就違背了將獨(dú)立文本單獨(dú)出來
的思想,任何的優(yōu)化都是平等的,可能會(huì)因小失大, - 考慮到如果是位置匹配之類的可以放入到多選結(jié)構(gòu)內(nèi),但是如果尾部是
$
,即行結(jié)尾標(biāo)記,則又將失去行結(jié)束錨點(diǎn)的優(yōu)化,也就是除了一些特殊的位置標(biāo)記,其他可以放入到多選結(jié)構(gòu)內(nèi),而一些單純的文本則不放入,將他們單獨(dú)分離出來,可以得到文本中必須出現(xiàn)字符或字符串優(yōu)化.
\G使用之前匹配結(jié)束的位置
-
regex = "\\G(?:\\d{5})*?(44\\d{3})?"
如果使用該表達(dá)式匹配文本,一旦匹配的文本長度為0,則將不會(huì)在進(jìn)行匹配,因?yàn)槭褂玫氖侵捌ヅ浣Y(jié)束的位置,傳動(dòng)裝置將不會(huì)驅(qū)動(dòng),使用引擎能知道下次在該處匹配也得到相同的結(jié)果,會(huì)無限循環(huán)
匹配44開頭的5位數(shù)美國郵政編碼(ZIP Codes)
- 目標(biāo)文本是類似這樣的
03824531449411615213441829505344272752010217443235
- 匹配的難題是在某次匹配失敗時(shí),傳動(dòng)裝置會(huì)驅(qū)動(dòng)前進(jìn)一個(gè)字符,再次進(jìn)行匹配,而如果此時(shí)的匹配成功顯然不是我們需要的,因?yàn)檫@個(gè)郵政編碼在數(shù)據(jù)中可能根本不存在,
- 我使用的辦法是在進(jìn)行匹配的開始,要保證左邊必須是5的倍數(shù)數(shù)字,所以使用正則表達(dá)式
(?<=^(?:\d{5})*)44\d{3}
,但這將使用逆序環(huán)視進(jìn)行掃描不定長度的文本,一些正則引擎可能不支持, - 另一個(gè)辦法是保證數(shù)據(jù)的協(xié)調(diào)性,出現(xiàn)匹配錯(cuò)誤數(shù)據(jù)的原因主要是傳動(dòng)裝置的驅(qū)動(dòng),導(dǎo)致了數(shù)據(jù)的錯(cuò)位,實(shí)際上如果不是從5的倍數(shù)的位置開始匹配,即使成功,也是沒有意義的數(shù)據(jù),所以可以使用一些表達(dá)式來跳過(匹配)不是44開頭的的5位數(shù)
ZIP Codes
,
3個(gè)辦法
-
[1235-9]\d{4}|\d[1235-9]\d{3}
(如果開頭44則失配) -
(?!44)\d{5}
(如果開頭不是44則匹配) -
(\d{5}*?)
使用忽略優(yōu)先量詞,當(dāng)我們使用的44\d{3}
不能匹配的時(shí)候,強(qiáng)制要求該表達(dá)式進(jìn)行匹配,這樣就可以進(jìn)行一次44開頭的5位數(shù)匹配,失敗,則直接跳過(匹配)不需要的5位數(shù),再次進(jìn)行嘗試,
總結(jié)
- 上面的辦法就可以保證每次在需要匹配我們需要的數(shù)據(jù)的時(shí)候,位置是從5的倍數(shù)的位置開始匹配的,因?yàn)槟鼙WC總是能夠匹配,
匹配失敗時(shí)候仍然會(huì)出現(xiàn)數(shù)據(jù)的不協(xié)調(diào)性
- 當(dāng)最后不存在任何以5的倍數(shù)位置開始的44開頭的5位數(shù)的
ZIP Codes
時(shí)(但存在其他的ZIP Codes
),這個(gè)時(shí)候會(huì)整體匹配失敗,接下里,數(shù)據(jù)的不協(xié)調(diào)性問題又出現(xiàn)了, - 接下來由于傳動(dòng)裝置的驅(qū)動(dòng),強(qiáng)制在下一個(gè)位置進(jìn)行嘗試匹配,這就導(dǎo)致在不是5的倍數(shù)的位置上進(jìn)行匹配,是沒有意義的,
- 所以此時(shí)的急救措施可能會(huì)對(duì)我們要匹配數(shù)據(jù)的表達(dá)式使用可選的匹配——
(44\d{3})?
, - 但是這也確實(shí)能夠解決這個(gè)問題,除了第三個(gè)表達(dá)式不可以,因?yàn)樗麑⒖偸悄軌虺晒Γ赡軙?huì)匹配0長度文本,仍然導(dǎo)致傳動(dòng)裝置的驅(qū)動(dòng),另外兩個(gè)的表達(dá)式都是匹配優(yōu)先的,當(dāng)最后匹配到文本結(jié)束時(shí),嘗試
(44\d{3})?
,其只能夠成功,所以也就不存在失配導(dǎo)致的數(shù)據(jù)不協(xié)調(diào)性了。
優(yōu)缺點(diǎn)
- 即使內(nèi)容中不存在任何我們需要的44開頭的5位數(shù)
ZIP Codes
,但它也總是能夠匹配成功, - 但是卻具有較高的匹配速度,因?yàn)椴恍枰厮荩ヅ溥^程中也不會(huì)有傳動(dòng)裝置的驅(qū)動(dòng)
推薦解決方法
- 更加通用的辦法是使用\G
正確表達(dá)式正確匹配一個(gè)字符串
-
"(\\.|[^"\\\n])*+"
該表達(dá)式使用占有優(yōu)先量詞純粹是為了提高報(bào)告匹配失敗的速度 -
"(\\.|[^"\n])*+"
該表達(dá)式必須使用占有優(yōu)先量詞,防止回溯使得第二個(gè)分支匹配\
關(guān)于正則引擎
-
NFA
支持一些DFA
不支持的功能
- 捕獲型括號(hào)
- 反向引用(是因?yàn)椴恢С?code>1)
- 忽略優(yōu)先量詞(因?yàn)?code>DFA盡量保證匹配當(dāng)前位置最長的文本,所以沒有意義)
- 所以也不會(huì)支持占有優(yōu)先量詞,和固化分組,和
3
的道理一樣 - 不支持環(huán)視
- 但是
DFA
具有匹配效率很快,穩(wěn)定,總是能得到確定的結(jié)果(匹配最左最長的文本),但其在編譯階段會(huì)花費(fèi)較長的時(shí)間和內(nèi)存 - 傳統(tǒng)型的
NFA
是控制能力最強(qiáng)的正則引擎,因此使用者可以使用該引擎的表達(dá)式主導(dǎo)的性質(zhì)來精確控制匹配過程。
傳統(tǒng)型NFA
的多選分支
- 它既不是匹配優(yōu)先的也不是忽略匹配優(yōu)先的,而是按照表達(dá)式在多選分支的順序逐個(gè)嘗試,當(dāng)當(dāng)前的表達(dá)式匹配失敗后,將嘗試下一個(gè)表達(dá)式,即為每個(gè)表達(dá)式匹配開始時(shí)候都保存一個(gè)備用狀態(tài),
- 正是因?yàn)槿绱耍匀绻麄溆玫谋磉_(dá)式存在多個(gè)可以與原文本匹配的可能,一定要控制好順序,
- 像這樣的多選分支沒有意義
a((ab)\*|a\*)
因?yàn)榈谝粋€(gè)子表達(dá)式(ab)*
永遠(yuǎn)也不會(huì)匹配失敗,
一些匹配上的細(xì)節(jié)
- 關(guān)于捕獲型括號(hào)
((regex)?)
在這個(gè)表達(dá)式中存在兩個(gè)group
,其中group1
和group2
都可能成功捕獲到regex
匹配的內(nèi)容,但是group1
還可能捕獲到長度為0的字符串,但此時(shí),group2
沒有被應(yīng)用,其值為NULL
用肯定環(huán)視
模擬固化分組
當(dāng)環(huán)視成功以后,其中的備用狀態(tài)將會(huì)被丟棄,所以可以使用回溯引用
來捕獲剛剛環(huán)視的內(nèi)容如(?=(regex))\1
,這個(gè)時(shí)候\1就是一段固定的文本值了,在對(duì)文本值\1
進(jìn)行匹配的過程中顯然不會(huì)保存任何備用狀態(tài)。所以使用\1
的匹配達(dá)到了一個(gè)固化分組
的匹配效果
-
(?>regex)
可以使用(?=(regex))\1
來模擬,但通常固化分組的效率要更高些,因?yàn)榄h(huán)視的嘗試匹配后,接下來\1還會(huì)在重復(fù)進(jìn)行一次匹配,只不過這次將會(huì)消費(fèi)文本.
關(guān)于regex
測(cè)試器
-
java
和intellij
的regex
都是采用當(dāng)前匹配的開始位置
-
regexr.com
的regex
采用之前匹配的結(jié)束位置
關(guān)于NFA
和DFA
-
NFA
表達(dá)式主導(dǎo) -
DFA
文本主導(dǎo) -
NFA
支持忽略優(yōu)先量詞,DFA
不支持, - 對(duì)于哪個(gè)分支應(yīng)答首先選擇?
優(yōu)先量詞將采取進(jìn)行嘗試
,而忽略優(yōu)先量詞采取跳過嘗試
- 備用狀態(tài),回溯進(jìn)行時(shí),應(yīng)該選取哪個(gè)保存的狀態(tài)?
當(dāng)本地失敗時(shí)將選擇最近保存的狀態(tài),使用的原則是LIFO
(后進(jìn)先出,類似棧)
簡單的判斷正則引擎
- 首先如果該引擎支持忽略優(yōu)先量詞,那么基本就能確定這是
traditional nfa
引擎了,因?yàn)?strong>忽略優(yōu)先量詞在dfa
中不支持,且在POSIX nfa
引擎中也沒有意義(我的猜測(cè)是這這個(gè)引擎的標(biāo)準(zhǔn)量詞可能默認(rèn)是貪婪型的),為了測(cè)試這一點(diǎn)可以使用模式nfa|nfa not
匹配nfa not
如果成功匹配的只是nfa
,那么則能確定這是traditional nfa
引擎,否則nfa not
都能匹配,那么只可能是POSIX nfa
或dfa
引擎了 - 如果之前的判斷排除它是
traditional nfa
引擎的可能,那么接下來要判斷它是POSIX nfa
還是dfa
引擎,dfa
引擎是不支持捕獲性括號(hào),所以自然也不支持回溯引用,但是一些混合使用兩種引擎的系統(tǒng),當(dāng)沒有使用捕獲型括號(hào),那么將使用dfa
引擎 - 所以單純的通過其是否支持某些特性來判斷一個(gè)引擎將過于草率,其實(shí)可以通過下面的這個(gè)測(cè)試?yán)蛹纯膳袛喑鍪?code>POSIX nfa還是
dfa
引擎
使用模式:
X(.+)+X
匹配文本:
XX-----------------------------------------------------------------------
如果匹配要花費(fèi)很長的時(shí)間,那就是
之前匹配的結(jié)束位置,還是當(dāng)前匹配的開始位置
在對(duì)一個(gè)文本進(jìn)行多次匹配時(shí),如果之前匹配的文本長度大于0,則下次匹配的時(shí)候?qū)⑹褂弥捌ヅ涞慕Y(jié)束位置,否則傳動(dòng)裝置將強(qiáng)行前進(jìn)到下一個(gè)字符,就使用當(dāng)前匹配的開始位置
匹配模式(?mode)
和作用域
模式
-
i
忽略大小寫匹配 -
x
寬松排列和注釋模式 -
s
點(diǎn)號(hào)通配模式 -
m
增強(qiáng)的行錨點(diǎn)模式
作用域
-
(otherRegex)
(?mode)(targetRegex)(?-mode)(otherRegex)
在上面的例子中(?mode)
會(huì)啟用該模式并作用于targetRegex
直到(?-mode)
將停用此功能 -
(otherRegex)
(?:(?mode)targetRegex)(otherRegex)
在上面的例子中(?mode)
會(huì)啟用該模式并作用于targetRegex
直到閉括號(hào)的結(jié)束 -
(otherRegex)
(?mode:targetRegex)(otherRegex)
上面表達(dá)式的一種簡寫方式,表示模式的修飾范圍只在括號(hào)內(nèi)有效
(page: 135)
Java regex 字符組集合操作
假如要匹配除元音字母的其他任意英語小寫字母,則可寫為
[[a-z]&&[^aeiou]]
[...]&&[^....]
來表示-
的集合操作
同時(shí)也可以使用環(huán)視來模擬此功能:
[a-z](?<![aeiou])
[a-z](?<=[^aeiou])
上面的意思是先匹配一個(gè)字母,然后再確保匹配好的字符不能是元音字母
(?![aeiou])[a-z]
(?=[^aeiou])[a-z]
上面的意思是先把光標(biāo)定位到除元音字母以外的任意字符的左邊,然后匹配小寫字母
Java使用regex的/x
模式,內(nèi)嵌注釋
String regex = "(?x)M" +
"#This is an Note\n" +
"A#666\n";
- 實(shí)際上得到的正則表達(dá)式是:
(?x)MA
- 也即
#
和Ln
包括它們之間的字符都將被視為注釋
Java字符串文本與正則表達(dá)式的關(guān)系
regex = "(?x)S S\t\\t\n\\n"
status | value |
---|---|
Java src text | (?x)S S\t\\t\n\\n |
Java compiled text |
(?x)S S Tab\t Ln\n
|
regex | (?x)SS\t\n |
因?yàn)?code>(/x)的模式的影響,忽略所有的空白字符作為最后的regex
,這可能會(huì)讓人有點(diǎn)疑惑,那為什么\t
和\n
依然存在,這是因?yàn)?code>\t和\n
分別是兩個(gè)字符\
, t
和\
, n
字符的組合,這些字符本身都不是空白字符,只是regex
用來匹配的時(shí)候會(huì)將\t
和\n
視為元序列字符,分別匹配制表符和換行符
環(huán)視
- 肯定順序環(huán)視
(?=)
- 肯定逆序環(huán)視
(?<=)
- 否定順序環(huán)視
(?!)
- 否定逆序環(huán)視
(?<!)
- 利用環(huán)視可以模擬一些流派上不支持的正則表達(dá)式符號(hào),
metacharacter | lookaround |
---|---|
</ |
(?<=\W)(?=\w) |
/> |
(?<!\W)(?!\w) |
\b |
</|/> , (?<=\W)(?=\w)|(?<=\w)(?=\W)
|
\B |
(?<=\W)(?=\W)|(?<=\w)(?=\w) |
但元字符卻具有更高的效率
為451545
類似的數(shù)值從右往左每3個(gè)數(shù)字添加一個(gè)逗號(hào),且最右邊不添加
真正解決這個(gè)問題應(yīng)該使用表達(dá)式
(?<=\d)(?=(?:\d{3})+\b)只能使用環(huán)視,因?yàn)樵谝粋€(gè)數(shù)值中要添加多次逗號(hào),
而通常的做法是只要每次匹配到3個(gè)數(shù)字且它們前面還有數(shù)字的話,它們的前面就應(yīng)該添加一個(gè)逗號(hào),前面有逗號(hào)的話,只需要逆序環(huán)視(?<=\d)即可,但是考慮到匹配后面3的整數(shù)倍的數(shù)值,只能使用順序環(huán)視,因?yàn)楹唵问褂?\d{3})+\b將會(huì)消費(fèi)后面的所有文本,所以只能添加一個(gè)逗號(hào),而且還會(huì)出錯(cuò),
應(yīng)該使用順序環(huán)視(?=(\d{3})+\b)可以重復(fù)檢查后面的文本,依次找出所有需要添加逗號(hào)的位置在這樣之所以使用
(?:...)
非捕獲型的括號(hào)是因?yàn)榉凑@個(gè)regex的該子表達(dá)式捕獲到的$1不會(huì)被使用,而且它效率更高,因?yàn)橐娌恍枰洃洸东@的文本。但是卻丟失了一定的可讀性
Intellij IDEA
的regex
的特點(diǎn)
- 對(duì)于匹配同一個(gè)位置,常常可被匹配到兩次,這很可能是因?yàn)樗雌ヅ湟粋€(gè)字符結(jié)束位置,也匹配一個(gè)字符的開始位置,所以同-個(gè)位置可能被視為2個(gè)位置----開始和結(jié)束
[ \t]*
和( *|\t*)
的區(qū)別
-
( *|\t*)
只能匹配sapce
或\t
的連續(xù)序列,而不能匹配它們的混合序列 -
[ \t]*
不但能匹配包括( *|\t*)
能夠匹配的內(nèi)容,還能匹配sapce
和\t
的混合序列 - 實(shí)際上
[ \t]*
和( |\t)*
邏輯上是等價(jià)的,但是字符組的效率卻更高一些
非捕獲型括號(hào)(?:
...)
-
(?:
...)
只分組不捕獲 -
(
...)
即分組又捕獲
如表達(dá)式([+-]?\d+(\.\d*)?)\s*([CF])
和([+-]?\d+(?:\.\d*)?)\s*([CF])
RegEx | ([+-]?\d+(\.\d*)?)\s*([CF]) |
([+-]?\d+(?:\.\d*)?)\s*([CF]) |
---|---|---|
\1 |
([+-]?\d+(\.\d*)?) |
([+-]?\d+(?:\.\d*)?) |
\2 |
(\.\d*) |
([CF]) |
\3 |
([CF]) |
NULL |
書中可能的錯(cuò)誤列表
- 但我們知道
First|1st
與(fir|1)st
表示的是同一個(gè)意思(page: 13)
-
\w
應(yīng)該還能匹配_
(page: 49)
- E-mail Message范本倒數(shù)第三行可能在一些英文單詞上少了一些空格
(page: 54)
- 下面的程序段可能存在過多的
}
字符(page: 57)
- 在a標(biāo)簽的href引用的鏈接應(yīng)該用雙引號(hào)包圍
(page: 74)
- 關(guān)于字符串文字的若干例子, 字符串文本
[\t\x2A]
在/x
模式下只能匹配*
(page: 102)
- 十進(jìn)制編碼015,應(yīng)該是八進(jìn)制編碼才對(duì)
(page: 115)
-
[^LMNOP]
通常等價(jià)于[\x00-KQ-\xFF]
而不是[\x00-kQ-\xFF]
(page: 119)
- 匹配
HTML Tag
,使用的表達(dá)式是<("[^"]"|'[^']'|[^">'])*+>
,這樣會(huì)匹配到<>
,所以應(yīng)該使用<("[^"]"|'[^']'|[^">'])++>
(page: 200)
疑問列表
- 表達(dá)式
(a)?b\1
還能匹配除文本aba
以外的其他文本嗎?(待研究)
API
- 元字符
(page: 32)
- 一些語法
(page: 114)
- 字母表
(page: 123)
子表達(dá)式定義
子表達(dá)式
是只正則表達(dá)式中的一部分,通常是括號(hào)內(nèi)的表達(dá)式
如^(Subject|Date):
中,(Subject|Date)
通常被視為一個(gè)子表達(dá)式
其中Subject
和Date
也算是子表達(dá)式。而且,嚴(yán)格來講
S
u
b
j
……
這些都算是子表達(dá)式。
匹配位置的元字符\B
,\b
,\<
,\>
- 它們都匹配一個(gè)位置
- 它們都是元字符序列
-
\B
匹配的位置在\W
與\W
或\w
與\w
之間 -
\b
匹配的位置在\W
與\w
或\w
與\W
之間 -
\<
匹配的位置在\W
與\w
之間 -
\>
匹配的位置在\w
與\W
之間
使用regex
檢索文本的精確度
取決于我們對(duì)需要檢索的文本的了解程度。
舉個(gè)極端的例子,要匹配一個(gè)文件內(nèi)的所有數(shù)字,顯然可以使用
\d
,但是如果我們清楚的明白我們的文件內(nèi)的內(nèi)容都是純數(shù)字的話,
可以簡單的使用.
關(guān)于grep
grep
會(huì)在檢查regex之前把換行符刪除掉,
然后再用regex與每行刪除完換行符剩下的內(nèi)容進(jìn)行匹配
關(guān)于在字符組中的元字符^
[^……]
將會(huì)匹配未被列出的任意字符,
而且只有當(dāng)^
出現(xiàn)在[
的最左邊時(shí),它才是一個(gè)元字符
[^^]
第二個(gè)^
就只是普通字符,這個(gè)模式的含義是它將匹配出除^
字符以外的任意字符
關(guān)于元字符^
和$
-
^
匹配行開頭 -
$
匹配行結(jié)尾
更具體的說明
但如果使用了增強(qiáng)的行錨點(diǎn)模式
- 那么
^
不但能狗匹配字符串開頭還能匹配每個(gè)換行符后面的位置(但不能匹配字符串結(jié)束位置) - 同樣
$
還能匹配每個(gè)換行符的開始位置(左邊) - 所以使用
\A
,可以總是匹配字符串的開頭 - 使用
\Z
和\z
,可以總是匹配字符串的結(jié)束
關(guān)于在字符組中的連字符-
-
[0-9A-Z_!.?]
能夠匹配一個(gè)數(shù)字,大寫字母,下劃線,驚嘆號(hào),點(diǎn)號(hào),或者是問號(hào)。 - 只有在字符組內(nèi)部,連字符才有可能是元字符----否則他就只能匹配普通的連字符號(hào),
- 當(dāng)不能形成一個(gè)范圍的時(shí)候,它就是一個(gè)普通的連字符號(hào)
- 如
[a-z-]
第二個(gè)連字符就將被解釋為一個(gè)普通的連字符號(hào)