簡(jiǎn)介:
本文是系列博客的第一篇,主要講解和分析正則表達(dá)式規(guī)則以及JAVA中原生正則表達(dá)式引擎的使用。在后續(xù)的文章中會(huì)涉及基于NFA的正則表達(dá)式引擎內(nèi)部的工作原理,并在此基礎(chǔ)上用1000行左右的JAVA代碼,實(shí)現(xiàn)一個(gè)支持常用功能的正則表達(dá)式引擎。它支持貪婪匹配和懶惰匹配;支持零寬度字符(如“\b”, “\B”);支持常用字符集(如“\d”, “\s”等);支持自定義字符集(“[a-f]”,“[^b-k] ”等);支持所有重復(fù)操作(“*”,“+”,“?”,“{n,m}”等);支持通配符(“. ”);支持運(yùn)算符本身的轉(zhuǎn)義字符(“\*”,“\.”等);支持命名捕獲組。本系列博客的目的是理解正則表達(dá)式的內(nèi)部工作原理,所以沒有考慮正則表達(dá)式引擎的工作效率以及正負(fù)斷言和非命名捕獲組等不常用的功能,后續(xù)的工作將會(huì)對(duì)其優(yōu)化并完備其功能。由于正則表達(dá)式引擎的實(shí)現(xiàn)需要運(yùn)用數(shù)據(jù)結(jié)構(gòu)中的相關(guān)內(nèi)容,閱讀本系列博客前請(qǐng)熟悉棧,隊(duì)列,圖以及JAVA中容器等相關(guān)知識(shí)。
歡迎探討,如有錯(cuò)誤敬請(qǐng)指正
如需轉(zhuǎn)載,請(qǐng)注明出處 http://www.cnblogs.com/nullzx/
1. 正則表達(dá)式的作用
正則表達(dá)式的功能就是在文本串中搜索特定模式的字符串。我們以下面方框中豆瓣電影網(wǎng)頁中給出的信息為例,我們想在這些文本中找出所有的日期信息,我們發(fā)現(xiàn)日期信息的字符格式在以下文本串中具有特定的格式,都是xxxx-xx-xx的模式(比如2017-01-27),這里的x表示一個(gè)具體的數(shù)字。所以我們搜索的字符串的格式就是“\d{4}-\d{2}-\d{2}”,在正則表達(dá)式中\(zhòng)d表示數(shù)字,{n}表示重復(fù)n次。
猜火車2 猜火車2 / 迷幻列車2(港) / T2:Trainspotting 2017-01-27(英國) / 伊萬·麥克格雷格 / 約翰尼·李·米勒 / 羅伯特·卡萊爾 / 艾文·布萊納 / 雪莉·亨德森 / 安杰拉·奈迪亞科娃 / 史蒂文·羅伯特森 / 戈登·肯尼迪 / 西蒙·韋爾 / 詹姆斯·卡沙莫 / 梁佩詩 / 阿塔·雅谷伯 / 埃文·威爾什 /... ... ... ... 7.8 (5392人評(píng)價(jià)) 寶貝老板 寶貝老板 / 娃娃老板 / 波士BB(港) 2017-03-12(邁阿密電影節(jié)) / 2017-03-31(美國) / 亞歷克·鮑德溫 / 邁爾斯·克里斯托弗·巴克什 / 吉米·坎摩爾 / 麗莎·庫卓 / 史蒂夫·布西密 / 托比·馬奎爾 / 詹姆斯·麥格拉思 / 康拉德·弗農(nóng) / 薇薇安·葉 / 小埃里克·貝爾 / 大衛(wèi)·索倫 / 伊迪·米爾曼... 8.3 (184408人評(píng)價(jià)) 逃出絕命鎮(zhèn) 逃出絕命鎮(zhèn) / 訪?嚇(港) 2017-01-23(圣丹斯電影節(jié)) / 2017-02-24(美國) / 丹尼爾·卡盧亞 / 艾莉森·威廉姆斯 / 凱瑟琳·基納 / 布萊德利·惠特福德 / 卡賴伯·蘭德里·瓊斯 / 馬庫斯·亨德森 / 貝蒂·加布里埃爾 / 勒凱斯·斯坦菲爾德 / 斯蒂芬·魯特 / 李雷爾·哈瓦瑞... 7.5 (51576人評(píng)價(jià)) |
由于排版的需要,以上文本框中的內(nèi)容比下圖實(shí)際處理數(shù)據(jù)中的內(nèi)容為基礎(chǔ)進(jìn)行了刪減
我們通過正則表達(dá)式測(cè)試工具進(jìn)行文本串中特定模式串\d{4}-\d{2}-\d{2}匹配,結(jié)果如下圖所示
通過得到的處理結(jié)果,我們搜索到了文本串中所有的日期信息。從這個(gè)例子我們可以看出正則表達(dá)式引擎的主要功能就是在給定的文本串中搜索符合正則表達(dá)規(guī)則的特定模式的字符串,而這個(gè)特定的模式是我們通過分析文本串中感興趣的信息總結(jié)得到的一般規(guī)律。比如要得到文本中電影的評(píng)分,字符串的格式就是“\d.\d”。
除了上述例子外,正則表達(dá)式還有很多應(yīng)用。例如,在注冊(cè)用戶時(shí),驗(yàn)證用戶輸入的郵箱是否合法;在網(wǎng)絡(luò)爬蟲技術(shù)中,爬取我們感興趣的相關(guān)內(nèi)容;編譯器設(shè)計(jì)中,我們還可以將正則表達(dá)式作為詞法分析器,等等。使用正則表達(dá)式能夠使我們更方便,更加高效的解決字符串模式匹配的相關(guān)問題,而不必為每一個(gè)問題單獨(dú)寫一個(gè)程序。這里我們所說的效率高,是指編寫程序的效率更高,而非程序的運(yùn)行效率。
我們的目的是寫一個(gè)正則表達(dá)式引擎,所以我們接下來就非常有必要了解一下正則表達(dá)式的一般規(guī)則。
2. JAVA正則表達(dá)式的規(guī)則
2.1 自定義字符集
[abc] | a或b或c |
[^abc] | 除了a,b,c的其它字符 |
[a-zA-Z] | 滿足a-z范圍的字符或A-Z范圍的字符 |
例子:下面的正則表達(dá)式會(huì)匹配兩個(gè)字符,第一個(gè)為小寫字母,第二個(gè)為數(shù)字,文本串中已捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“[a-z][0-9]”
文本串內(nèi)容:“absef809sefdk1dfes12389”
2.2 已定義字符集
. | 可以匹配非換行符以外的任何字符,能否匹配換行符是可配置的 |
\d | 數(shù)字,等價(jià)于[0-9] |
\D | 非數(shù)字,等價(jià)于[^0-9] |
\s | 空白符,等價(jià)于[ \t\n\x0B\f\r] |
\S | 非空白符,等價(jià)于[^\s] |
\w | 字母、數(shù)字或下劃線,等價(jià)于[a-zA-Z_0-9] |
\W | 非字母和數(shù)字,等價(jià)于[^\w] |
例子:下面的正則表達(dá)式會(huì)匹配以非空白字符開頭和非空白字符結(jié)尾,中間是“abc”的字符串,總共需要捕獲5個(gè)字符,文本串中已捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“\Sabc\S”
文本串內(nèi)容:“abcd abc defabcyjkabc”
2.3 轉(zhuǎn)義字符(不解釋)
\t | The tab character ('\u0009') |
\n | The newline (line feed) character ('\u000A') |
\r | The carriage-return character ('\u000D') |
\f | The form-feed character ('\u000C') |
\a | The alert (bell) character ('\u0007') |
\e | The escape character ('\u001B') |
\cx | The control character corresponding to x |
2.4 零寬度邊界字符
零寬度邊界字符,只會(huì)匹配一個(gè)位置而不會(huì)占有字符
^ | 行開始 |
$ | 行結(jié)束 |
\b | 單詞的開始邊界或結(jié)束邊界 |
\B | 非單詞的邊界 |
例子:下面的正則表達(dá)式會(huì)匹配字符串“abc”,并且要求第一個(gè)字符‘a(chǎn)’的前面不是字母字符和數(shù)字字符,最后一個(gè)字符‘c’的后面不是字母字符和數(shù)字字符。正則表達(dá)式總共需要捕獲3個(gè)字符,文本串中已捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“\babc\b”
文本串內(nèi)容:“abc dabcd abc abcd -abc”
2.5 貪婪重復(fù)模式(盡量多重復(fù))
X表示一個(gè)合法的正則表達(dá)式
X? | X重復(fù)一次或0次 |
X* | X,重復(fù)0次或多次 |
X+ | X重復(fù)至少1次 |
X{n} | X重復(fù)剛好n次 |
X{n,} | X重復(fù)至少n次 |
X{n,m} | X重復(fù)至少n次,最多m次 |
例子:下面的正則表達(dá)式會(huì)匹配以a開頭和a界結(jié)尾的,中間有盡可能多的其它字符,且其它字符要求至少有一次,文本串中已捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“a.+a”
文本串內(nèi)容:“zxyabcdefasseaa09876”
2.6 懶惰重復(fù)模式(盡量少重復(fù))
X?? | X重復(fù)一次或0次 |
X*? | X,重復(fù)0次或多次 |
X+? | X重復(fù)至少1次 |
X{n}? | X重復(fù)剛好n次 |
X{n,}? | X重復(fù)至少n次 |
X{n,m}? | X重復(fù)至少n次,最多m次 |
例子:下面的正則表達(dá)式會(huì)匹配以a開頭和a界結(jié)尾的,中間有盡可能少的其它任意字符,且其它任意字符要求至少有一次。文本串中已捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“a.+?a”
文本串內(nèi)容:“zxyabcdefasseaa09876”
2.7 邏輯運(yùn)算符
X和Y分別表示兩個(gè)正則表達(dá)式
XY先滿足正則表達(dá)式X,然后滿足正則表達(dá)式Y(jié)的正則表達(dá)式
X|Y 滿足正則表達(dá)式X或滿足正則表達(dá)式Y(jié)的正則表達(dá)式
注意優(yōu)先級(jí),X|YZ 等價(jià)于 X|(YZ),而(X|Y)Z表示XZ|YZ
正則表達(dá)式:“a(b|c)d”
文本串內(nèi)容:“zxyabcdefacdeaabd876”
2.8 括號(hào)
在正則表達(dá)式中的作用有兩個(gè),一個(gè)和四則運(yùn)算中的括號(hào)相同,用來改變優(yōu)先級(jí),另一個(gè)用于分組捕獲。分組捕獲又分為兩種,一種是自定義命名的分組,還有一種是未命名的分組(或者稱為自動(dòng)編號(hào)分組)。
命名分組的格式為:(?<name>X),其中X表示一個(gè)正則表達(dá)式
例子:下面的正則表達(dá)式表示已數(shù)字開頭,中間是字母,以數(shù)字結(jié)尾的字符串。名為letter的捕獲組捕獲符合該正則表達(dá)式中間為字母的部分。文本串中捕獲的內(nèi)容用紅色表示。
正則表達(dá)式:“\d+(?<letter>[a-zA-Z]+)\d+”
文本串內(nèi)容:“0123ab456def gisd4ZDG6zz”
名為letter的捕獲組中的內(nèi)容為:“0123ab456def gisd4ZDG6zz”
對(duì)于未命名分組,每一對(duì)括號(hào)實(shí)際上都是一對(duì)分組,正則表達(dá)式引擎會(huì)在編譯該表達(dá)式的時(shí)候會(huì)從左到右掃描正則表達(dá)式,對(duì)未命名分組進(jìn)行編號(hào)。遇到的第1個(gè)左括號(hào)(和相應(yīng)匹配的右括號(hào))是第1組,遇到的第2個(gè)左括號(hào)(和相應(yīng)匹配的右括號(hào))是第2組,……。第0組的內(nèi)容匹配的是整個(gè)正則表達(dá)式。實(shí)際上組號(hào)分配過程是要從左向右掃描兩遍的:第一遍只給未命名組分配,第二遍只給自定義命名組分配,也就是說自定義命名分組也是有編號(hào)的,且所有自定義命名組的組號(hào)都大于未命名的組號(hào)。
2.9 特殊字符的匹配
對(duì)于一些在正則表達(dá)式中具有特殊含義的特殊字符,比如“{”,“*”“\”等等,如果我們想在文本串中捕獲它們,就只能通過轉(zhuǎn)義的方式。比如我們想匹配文本串中以“{”花括號(hào)開頭,花括號(hào)結(jié)尾“}”,中間有任意數(shù)量其它字符,且其它任意字符盡可能少。我們的正則表達(dá)式就可以寫成“\{.*?\}”,它可以匹配以下字符串中的“abcde{fghi{jklmn}op}xyz”。
正則表達(dá)式:“\\.*?\\” 表示已“\”開頭和“\”結(jié)尾中間為任意數(shù)量且盡可能少的其它字符。它可以匹配以下字符串中的“abcde\fghi\jklmn\op\xyz”
2.10 零寬斷言
在某些特殊的情況下,如果我們只是想要匹配某個(gè)字符有(或者沒有)出現(xiàn),但并不想去捕獲它的時(shí)候,我們就需要零寬度斷言。零寬度斷言和\b等零寬度字符一樣,都是匹配一個(gè)位置,并不消耗字符,但零寬度斷言可以是由表達(dá)式構(gòu)成,功能也就更加強(qiáng)大。零寬度斷言分為四種情況。
零寬度正預(yù)測(cè)斷言
“預(yù)測(cè)”表示向匹配內(nèi)容的后方看,“正”表示匹配的意思
一般格式:“exp1(?=exp2)”
含義:匹配文本串中符合正則表達(dá)式exp1的內(nèi)容,且文本串中已匹配exp1的字符串的后面必須匹配exp2,但不消耗文本串中匹配exp2的字符,且結(jié)果中不捕獲exp2匹配的內(nèi)容。不消耗匹配exp2的字符的意思是,下一次搜索從文本串中匹配exp1的后面開始,而不是從匹配exp2的后面開始。注意,exp2右括號(hào)后面一般不能再跟正則表達(dá)式否則,不會(huì)匹配到任何東西。
例子:下面的正則表達(dá)式會(huì)匹配一個(gè)單詞,且這個(gè)單詞必須以ing結(jié)尾。文本串中捕獲的內(nèi)容用紅色標(biāo)示,綠色表示正預(yù)言的匹配。
正則表達(dá)式:"\b\w+(?=ing\b)"
文本串內(nèi)容:"i am singing while you are dancing"
注意,正則表達(dá)式不能寫成"\b\w+(?=ing)\b",這樣不會(huì)匹配任何字符串,因?yàn)椴淮嬖谌魏我粋€(gè)字符串后面是ing,同時(shí)又要求ing是結(jié)束的邊界(由于不消耗文本串中的ing)。
同理,"\b\w+(?=ing)ing" 等價(jià)于 "\b\w+ing"
零寬度正回顧斷言
“回顧”表示向匹配內(nèi)容的前方看,“正”表示匹配的意思
一般格式:“(?<=exp2)exp1”
含義:匹配文本串中符合正則表達(dá)式exp1的內(nèi)容,且文本串中已匹配exp1的內(nèi)容前面必須匹配exp2,但在結(jié)果中不捕獲exp2的匹配內(nèi)容。注意,exp2左括號(hào)前面不能再跟正則表達(dá)式否則,不會(huì)匹配到任何東西。
例子:下面的正則表達(dá)式會(huì)匹配一個(gè)單詞,且這個(gè)單詞必須以anti開頭。文本串中捕獲的內(nèi)容用紅色標(biāo)示,綠色表示正回顧的匹配。
正則表達(dá)式:"(?<=\banti)\w+\b"
文本串內(nèi)容:"this is an antibody, not an antivirus"
注意,正則表達(dá)式不能寫成"\b(?<=anti)\w+\b",原因和正預(yù)測(cè)是一樣的,因?yàn)椤癨b”和“anti”是互斥的,也就是說沒有一個(gè)字符同時(shí)滿足即使“\b”又是“anti”。
零寬度負(fù)預(yù)測(cè)斷言
“預(yù)測(cè)”表示向匹配內(nèi)容的后方看,“負(fù)”表示不匹配的意思
一般格式:“exp1(?!exp2)”
含義:匹配文本串中符合正則表達(dá)式exp1的內(nèi)容,且匹配exp1的內(nèi)容后面不能匹配exp2。和正預(yù)測(cè)不同,我們一般可以構(gòu)成如下的正則表達(dá)式exp1(?!exp2)exp3,只要exp2和exp3不相同就不會(huì)構(gòu)成互斥。
例子:下面的正則表達(dá)式會(huì)匹配一個(gè)單詞,且這個(gè)單詞不能以ing結(jié)尾。文本串中捕獲的內(nèi)容用紅色標(biāo)示。
例子:下面的正則表達(dá)式會(huì)匹配一個(gè)單詞,且這個(gè)單詞不能以anti開頭。文本串中捕獲的內(nèi)容用紅色標(biāo)示。
正則表達(dá)式:"\\b(?!anti)\\w+\\b"
文本串:"this is an antibody, not an antivirus"
零寬度負(fù)回顧斷言
“回顧”表示向匹配內(nèi)容的前方看,“負(fù)”表示不匹配的意思
一般格式:“(?<!exp2)exp1”
含義:捕獲文本串中符合正則表達(dá)式exp1的內(nèi)容,且捕獲的內(nèi)容前面不能匹配exp2。和正回顧不同,我們一般可以構(gòu)成如下的正則表達(dá)式“exp3(?<!exp2)exp1”,只要exp2和exp3不相同就會(huì)構(gòu)成互斥。
例子:下面的正則表達(dá)式會(huì)匹配一個(gè)單詞,且這個(gè)單詞不能以ing結(jié)尾。文本串中捕獲的內(nèi)容用紅色標(biāo)示。
正則表達(dá)式:"\\b\\w+(?<!ing)\\b";
文本串:"i am singing , you are danceing";
3. JAVA中正則表達(dá)式引擎的使用
對(duì)同一個(gè)正則表達(dá)式,從鍵盤上輸入的形式和程序中由字符串表示的正則表達(dá)式的形式是不同的。比如我們最開始舉例時(shí)使用的正則表達(dá)式 \d{4}-\d{2}-\d{2} ,在JAVA中用字符串表示的形式如下
String reg = “\\d{4}-\\d{2}-\\{2}”
因?yàn)樵谧址?,需要用兩個(gè)“\\”表示一個(gè)“\”
JAVA中使用正則表達(dá)式主要涉及到兩個(gè)類,一個(gè)是Pattern類,另一個(gè)是Matcher類,他們都位于java.util.regex包中。Pattern主要的功能就是將正則表達(dá)式轉(zhuǎn)換成NFA(不確定有限自動(dòng)狀態(tài)機(jī))或者DFA(確定有限自動(dòng)狀態(tài)機(jī))。Matcher的作用是通過Pattern產(chǎn)生的FNA或DFA對(duì)文本串進(jìn)行匹配。
Pattern類的構(gòu)造函數(shù):
public static Pattern compile(String regex)
public static Pattern compile(String regex, int flags)
第二個(gè)構(gòu)造函數(shù)中的flag,可以是下列屬性值的組合,它們會(huì)影響匹配結(jié)果。
| CANON_EQ Enables canonical equivalence. |
| CASE_INSENSITIVE 大小寫不敏感 |
| COMMENTS 允許正則表達(dá)式中出現(xiàn)注釋 默認(rèn)情況下正則表達(dá)式中不允許出現(xiàn)正則表達(dá)式規(guī)定的注釋 |
| DOTALL “.”可以匹配任何字符 默認(rèn)情況下不能匹配 “\r\n”和“\n” |
| LITERAL 該模式下將轉(zhuǎn)義字符就表示字符本身 “\\d”就表示一個(gè)“\”和“d”而不表示數(shù)字的字符集 |
| MULTILINE 多行模式:^表示字符串中每一行的開始,$表示字符串中每一行的結(jié)束 默認(rèn)情況下:^表示字符串的開始,$表示字符串的結(jié)束 |
| UNICODE_CASE Enables Unicode-aware case folding. |
| UNICODE_CHARACTER_CLASS 使用該選項(xiàng)使得原先已定義好的字符集兼容Unicode編碼 |
| UNIX_LINES 類Unix下的換行:“\n” 默認(rèn)情況下使用Windows下的換行,即:“\r\n”或者 “\n” |
Pattern類下還有一個(gè)matcher方法,我們通過該matcher方法產(chǎn)生Matcher對(duì)象,該方法參數(shù)表示待匹配的文本串。
public Matcher matcher(CharSequence input)
Matcher下的find方法用于對(duì)文本串的匹配,如果發(fā)現(xiàn)匹配就返回真,當(dāng)再次調(diào)用find函數(shù)時(shí),會(huì)從上次已匹配的位置繼續(xù)搜索。
Matcher下的group方法用于返回匹配的字符串,start和end方法用于返回匹配的字符串在文本串中的開始和結(jié)束位置,注意不包含結(jié)束位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package javalearning; import java.util.regex.Matcher; import java.util.regex.Pattern; public class RegularExpTest { public static void main(String[] args){ String reg = "[a-z]((?<digtal>\\d+)(b|c)d)[A-Z]" ; String txt = "zdfasdfre1234bdXrt" ; Pattern p = Pattern.compile(reg); Matcher m = p.matcher(txt); while (m.find()){ System.out.println( "--------匹配結(jié)果----------" ); System.out.printf( "[%2d, %2d) : %s\n" , m.start(), m.end(), m.group()); System.out.println( "--------自動(dòng)命名組匹配結(jié)果--------" ); for ( int i = 0 ; i < m.groupCount(); i++){ System.out.printf( "group %2d [%2d, %2d) : %s\n" ,i, m.start(i), m.end(i), m.group(i)); } System.out.println( "--------自定義命名組匹配結(jié)果--------" ); System.out.printf( "digtal [%2d, %2d) : %s\n" , m.start( "digtal" ), m.end( "digtal" ), m.group( "digtal" )); System.out.println(); System.out.println(); System.out.println(); } } } |
運(yùn)行結(jié)果
--------匹配結(jié)果---------- [ 8, 16) : e1234bdX --------自動(dòng)命名組匹配結(jié)果-------- group 0 [ 8, 16) : e1234bdX group 1 [ 9, 15) : 1234bd group 2 [ 9, 13) : 1234 --------自定義命名組匹配結(jié)果-------- digtal [ 9, 13) : 1234 |
http://www.cnblogs.com/nullzx/p/7092157.html