寫正則表達(dá)式的正確姿勢

前言

上一篇文章我們學(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é)果中找到我們想要的信息了。

  1. 判斷target整串是否符合正則表達(dá)式
  boolean isMatch = m.matchers();
  1. 獲取所有匹配的子串
  List<String> result = new ArrayList<String>();
  m.reset();
  while(m.find()) {
    result.add(target.subString(m.start(), m.end());
  }
  1. 判斷target內(nèi)是否有符合正則規(guī)則的子串
boolean hasMatch = m.lookingAt();
  1. 替換符合正則規(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ú)占)。
聽名字可能不知道是干啥,我們來分別介紹下。

  1. 貪婪模式是默認(rèn)的模式,正常使用數(shù)量詞時就是貪婪模式,他會盡可能多的匹配字符串。說白了就是量詞優(yōu)先。
  2. 懶惰模式,量詞后面加?。盡可能少的匹配被量詞修飾的字符串。
  3. 獨(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)化。

  1. 避免重新編譯
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();
  1. 不要濫用括號,使用非捕獲型括號
    Java中的括號表示捕獲組的概念,捕獲組在匹配時會有一些引用,造成一些額外的開銷,因此能不用括號盡量不用括號。另外,當(dāng)我們要使用括號表示一個組的時候,如果我們不需要引用括號內(nèi)的文本,我們可以使用非捕獲型括號:(?:)。

  2. 量詞等價轉(zhuǎn)換
    對java來說:\d\d\d\d要比\d{4}快(Perl、Python、PHP正好相反)
    ====比={4}要快(Perl、Ruby和.NET優(yōu)化手段更高級,兩者一樣快)
    另外,使用正確的量詞(+、*、?、{n,m}),如果能夠限定長度,那就最好了。

  3. 不要濫用字符組
    如果像這樣的字符組”[:]“,里面只有字符,那么這樣程序就有處理字符組的代價。使用.和這樣的原符號時也不要用[.][]來轉(zhuǎn)義,我們應(yīng)該使用轉(zhuǎn)義符來轉(zhuǎn)義,而不是用字符組。

  4. 使用邊界匹配符
    使用正確的邊界匹配符(^、$、\b、\B等),限定搜索字符串位置

  5. 避免過多回溯
    首先優(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)生回溯)。

  6. 使用工具驗(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á)式這本書。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容