前言
上一篇文章我們學(xué)習(xí)了正則表達(dá)式原理,這次我們學(xué)習(xí)下怎么寫正則表達(dá)式。這里,我們不會學(xué)習(xí)正則表達(dá)式的各種符號,如果不經(jīng)常寫,那么肯定是會忘的。到時候如果要想寫那么還需我們再去查資料。這里我們主要學(xué)習(xí)下在Java中如何使用正則,以及怎么寫出高效的正則表達(dá)式。
在Java中使用正則
先來干的,說下在Java中如何使用正則表達(dá)式。
通常情況下,我們做一個字符串的分割匹配之類的可能就直接使用String方法自帶的api來完成了。這里我們學(xué)習(xí)下正則表達(dá)式的實(shí)際使用流程,以及實(shí)際應(yīng)用場景的使用。
在正則表達(dá)式原理中,我們知道對于一個正則表達(dá)式,計算機(jī)需要把這個正則表達(dá)式翻譯成一個自動機(jī)。在Java中這個自動機(jī)就是:Pattern。所以第一步就是將正則編譯成Pattern。
Pattern p = Pattern.compile("regex");
然后我們用我們的目標(biāo)字符串去匹配。得到一個匹配結(jié)果。Java的正則原理是基于NFA的,支持的能力很全面。因此匹配結(jié)果中會有很多的信息。而這個結(jié)果在Java中就是Matcher,匹配器。這個匹配結(jié)果包含了我們想要的所有匹配信息。
String target = "targetString";
Matcher m = p.matcher(target);
現(xiàn)在我們拿到了匹配結(jié)果,這時候就可以從匹配結(jié)果中找到我們想要的信息了。
- 判斷target整串是否符合正則表達(dá)式
boolean isMatch = m.matchers();
- 獲取所有匹配的子串
List<String> result = new ArrayList<String>();
m.reset();
while(m.find()) {
result.add(target.subString(m.start(), m.end());
}
- 判斷target內(nèi)是否有符合正則規(guī)則的子串
boolean hasMatch = m.lookingAt();
- 替換符合正則規(guī)則的子串
String replaceAll = m.replaceAll("xxx");
String replaceFirst = m.replaceFirst("xxx");
注意事項(xiàng):
- Matcher的matches(),lookingAt(),find()都會受到檢索范圍的影響。
- find(int start),replaceAll(),replaceFirst(),reset()不受影響。
- 另外Matcher的start和end屬性是整個串的index,與檢索范圍無關(guān)。
- 可以使用region(int start, int end)來確定要檢索的范圍。
Java中正則的數(shù)量詞模式
我們知道Java的量詞有*和+。那么數(shù)量詞模式又是什么呢?
正則表達(dá)式原理中我們知道NFA在匹配的時候需要兩個原則,1是量詞的優(yōu)先類型,2是后進(jìn)先出(回溯路徑)。因?yàn)榱吭~的優(yōu)先類型不同可能得到的匹配結(jié)果,和匹配的速度也不相同。
Java有三種數(shù)量詞模式:Greedy(貪婪),Reluctant(懶惰),Possessive(獨(dú)占)。
聽名字可能不知道是干啥,我們來分別介紹下。
- 貪婪模式是默認(rèn)的模式,正常使用數(shù)量詞時就是貪婪模式,他會盡可能多的匹配字符串。說白了就是量詞優(yōu)先。
- 懶惰模式,量詞后面加?。盡可能少的匹配被量詞修飾的字符串。
- 獨(dú)占模式,量詞后面加+,與貪婪相同量詞優(yōu)先,盡可能多匹配字符串,但是不會回溯。
舉個例子,對于正則表達(dá)式(".*")來說,三種表達(dá)如下:
String target = "start \"hello world\" haha \"hello java\" 666 \"hello android\"";
Pattern greedy = Pattern.compile("\".*\"");
Pattern reluctant = Pattern.compile("\".*?\"");
Pattern possessive = Pattern.compile("\".*+\"");
我們可以看到這個正則表達(dá)式的意思就是要匹配雙引號以及雙引號包含的內(nèi)容。那么這三種寫法的結(jié)果分別是什么呢?
greedy的匹配結(jié)果:"hello world" haha "hello java" 666 "hello android"
reluctant的匹配結(jié)果:"hello world", "hello java", "hello android"(三個結(jié)果)
possessive的匹配結(jié)果:無
對于貪婪模式,盡可能多的匹配,因?yàn)槭牵?* ),所以最后的引號也會匹配到(.* )中,這時明顯匹配失敗了,這時就會回溯到上一個選擇的路徑就是對于引號的匹配是用(.*)還是(" ),這時選擇(")匹配成功得到 "hello world" haha "hello java" 666 "hello android"。
懶惰模式是盡可能少的匹配量詞修飾的字符串,因此會得到三個符合要求的結(jié)果。
而獨(dú)占模式就是匹配失敗后不會產(chǎn)生回溯因此匹配結(jié)果是空。
高效的使用正則表達(dá)式
優(yōu)化使用正則表達(dá)式主要是從兩點(diǎn)出發(fā),1是優(yōu)化正則表達(dá)式本身,我們知道NFA型的正則表達(dá)式是以正則為主導(dǎo)的,正則表達(dá)式的寫法不同對于匹配的速度還是有影響的。優(yōu)化正則表達(dá)式本身的思路就是減少回溯,僅可能精準(zhǔn)的匹配想要匹配的字符。2、使用過程的優(yōu)化,工具本身帶有的給正則的屬性以及自身使用正則的習(xí)慣優(yōu)化。
- 避免重新編譯
String target0 = "target0";
String target1 = "target1";
//這樣的寫法"abc“這個正則會被編譯兩次
target0.matcher("abc");
target1.matcher("abc");
//"abc"只會被編譯一次
Pattern p = Pattern.compline("abc”);
boolean isMatch0 = p.matcher(target0).matchers();
boolean isMatch1 = p.matcher(target1).matchers();
不要濫用括號,使用非捕獲型括號
Java中的括號表示捕獲組的概念,捕獲組在匹配時會有一些引用,造成一些額外的開銷,因此能不用括號盡量不用括號。另外,當(dāng)我們要使用括號表示一個組的時候,如果我們不需要引用括號內(nèi)的文本,我們可以使用非捕獲型括號:(?:)。量詞等價轉(zhuǎn)換
對java來說:\d\d\d\d要比\d{4}快(Perl、Python、PHP正好相反)
====比={4}要快(Perl、Ruby和.NET優(yōu)化手段更高級,兩者一樣快)
另外,使用正確的量詞(+、*、?、{n,m}),如果能夠限定長度,那就最好了。不要濫用字符組
如果像這樣的字符組”[:]“,里面只有字符,那么這樣程序就有處理字符組的代價。使用.和這樣的原符號時也不要用[.][]來轉(zhuǎn)義,我們應(yīng)該使用轉(zhuǎn)義符來轉(zhuǎn)義,而不是用字符組。使用邊界匹配符
使用正確的邊界匹配符(^、$、\b、\B等),限定搜索字符串位置避免過多回溯
首先優(yōu)化量詞的使用,對于特定的業(yè)務(wù)場景,查看是需要先優(yōu)先量詞還是忽略量詞,對于java來說就是使用貪婪模式還是使用懶惰模式。
另外警惕過度回溯,比如”([abc]|[aef])+“這個正則表達(dá)式匹配”aaaaaaaaaaaaaa1"的時候耗時會非常的長,因?yàn)樗谧詈蟀l(fā)現(xiàn)1匹配的時候,會回溯到上一個a,然后嘗試用[aef]匹配,然后[aef]中有a,然后再去匹配1,發(fā)現(xiàn)不匹配,又回退到倒數(shù)第二個a,依次類推,回溯的次數(shù)變成了指數(shù)級。解決方式,第二個去掉a(會回溯一次),或者使用獨(dú)占模式([abc][aef])++(不會產(chǎn)生回溯)。使用工具驗(yàn)證
檢測修改,有時你認(rèn)為的優(yōu)化可能禁止了其他你不知道的已經(jīng)生效的優(yōu)化?;蛘吣銉?yōu)化了正則,但是正確性卻不存在了。附上一個調(diào)試正則表達(dá)式的網(wǎng)址:http://regex.zjmainstay.cn/。
總結(jié)
正則表達(dá)式是個很強(qiáng)大的東西,雖然平時我們只是用了它皮毛,但是如果使用不當(dāng)還是會遇到一些問題的。因此了解它的原理,知道如何正確使用它還是很有必要的。這里我們也只是大致學(xué)習(xí)了平時自己不了解的正則相關(guān)的知識,當(dāng)然還有很多正則相關(guān)的內(nèi)容這里是沒有覆蓋到的,感興趣的同學(xué)可以去看精通正則表達(dá)式這本書。