javaRegex 非擷取的群組詳解

來源:互聯網
上載者:User

這幾天看了下Regex,對非擷取的群組(non-capturing)進行下總結。
主要總結 1個 + 2組  一共5個。
(?:X) (?=X) (?<=X) (?!X) (?<!X)

一、先從(?:)非擷取的群組說起。
下面由一個例子引出非擷取的群組。

有兩個金額:8899¥ 和 6688$ 。顯然,前一個是8899元的人民幣,後一個是6688元的美元。我現在需要一個正則,要求提煉出它們的貨幣金額和貨幣種類。正則可以這寫:(\\d)+([¥$])$  (在java中測試,所以多了逸出字元'\')
測試程式如下:

    

[java] view
plaincopy

  1. Pattern p = Pattern.compile("(\\d+)([¥$])$");  
  2.      String str = "8899¥";  
  3.      Matcher m = p.matcher(str);  
  4.      if(m.matches()){  
  5.       System.out.println("貨幣金額: " + m.group(1));  
  6.       System.out.println("貨幣種類: " + m.group(2));  
  7.      }  

輸出結果為:
貨幣金額: 8899
貨幣種類: ¥

OK,滿足了要求。這裡的正則分成了兩個組,一個是(\\d+),一個是([¥$]),前一個組匹配貨幣金額,後一個組匹配貨幣種類。

現在,我需要這個正則可以匹配浮點數。如8899.56¥。我們都知道,現在少於一元錢基本上買不到東西了,所以我希望忽略小數部分,正則還是提煉出 8899 和 ¥。
那麼正則如下:
[code="java"](\\d+)(\\.?)(\\d+)([¥$])$[/code]
這裡用括弧分了四組,所以要輸出貨幣金額的整數部分和貨幣種類,要分別輸了group(1),group(4)了。如果輸出部分和正則是分開的,我希望只修改正則而不去修改輸出部分的代碼,也就是還是用group(1),group(2)作為輸出。由此可以引出非擷取的群組(?:)。
把前面的正則修改為:
[code="java"](\\d+)(?:\\.?)(?:\\d+)([¥$])$[/code]
這樣,還是用group(1),group(2)做為輸出,同樣輸出了 8899 和 ¥
這個正則的中間兩個組用到的就是非擷取的群組(?:),它可以理解為只分組而不捕獲。

二、(?=)和(?<=)
有的資料把它們叫做肯定式向前尋找和肯定式向後尋找;
有的資料也叫做肯定順序環視和肯定逆序環視。

1、姑且不理它們的名稱,看下面的例子:

   

[java] view
plaincopy

  1. Pattern p = Pattern.compile("[0-9a-z]{2}(?=aa)");  
  2.    String str = "12332aa438aaf";  
  3.    
  4.    Matcher m = p.matcher(str);  
  5.    while(m.find()){  
  6.      System.out.println(m.group());  
  7.    }  

這段程式輸出32 38
這個正則的意思是:匹配這麼一個字串,它要滿足:是兩位字元(數字,或字母),且[color=red]後面[/color]緊跟著兩個a。
分析一下:
32aa  這個子串 滿足這個條件,所以可以匹配到,又因為 (?=) 的部分是不捕獲的,所以輸出的只是 32,不包括aa。同理 38aa 也匹配這個正則,而輸出僅是 38。

再深入看一下:
當str第一次匹配成功輸出 32 後,程式要繼續向後尋找是否還有匹配的其它子串。那麼這時應該從 32aa 的後一位開始向後尋找,還是從 32 的後一位呢?也就是從索引 5 開始還是從 7 開始呢?有人可能想到是從 32aa 的下一位開始往後找,因為 32aa 匹配了正則,所以下一位當然是它的後面也就是從 4 開始。但實際上是從 32 的後一位也就是第一個 a 開始往後找。原因還是 (?=) 是非捕獲的。
查閱API文檔是這麼注釋的:

(?=X) X, via zero-width positive lookahead

可見zero-width(零寬度)說的就是這個意思。

現在,把字串寫的更有意思些:str = "aaaaaaaa";
看一下它的輸出: aa aa aa
分析一下:
這個字串一共有8個a。
第一次匹配比較容易找到,那就是前四個:aaaa ,當然第三和第四個 a 是不捕獲的,所以輸出是第一和第二個a;
接著繼續尋找,這時是從第三個a開始,三到六,這4個a區配到了,所以輸出第三和第四個a;
接著繼續尋找,這時是從第五個a開始,五到八,這4個a區配到了,所以輸出第五和第六個a;
接著往後尋找,這時是從第七個a開始,顯然,第七和第八個a,不滿足正則的匹配條件,尋找結束。
我們再延伸一下,剛說的情況的是(?=)放在捕獲的字串後面,它如果放在前面又是什麼結果呢?
例子換成:

   

[java] view
plaincopy

  1. Pattern p = Pattern.compile("(?=hopeful)hope");  
  2.     String str = "hopeful";  
  3.     Matcher m = p.matcher(str);  
  4.     while(m.find()){  
  5.       System.out.println(m.group());  
  6.     }  

它的輸出是hope。
正則的意思是:是否能匹配hopeful,如果能,則捕獲hopeful中的hope。當然繼續向後尋找匹配的子串,是從f開始。
比較一下可以看出,(?=hopeful)hope 和 hope(?=ful),兩個正則的效果其實是一樣的。

2、下面說一下 (?<=)
把正則改一下,
    Pattern p = Pattern.compile("(?<=aa)[0-9a-z]{2}");
字串還是str = "12332aa438aaf";
它的輸出:43。

這個正則的意思是:匹配這麼一個字串,它要滿足:是兩位字元(數字或字母),且[color=red]前面[/color]緊跟的是兩個字母 a 。

同樣,深入一下,把str換成str = "aaaaaaaa";看一下輸出是什麼,同樣也是:aa aa aa
分析一下:
第一次匹配不用說,是前四個a,輸出的是第三和第四個a;
繼續向後尋找,從第五個a開始,程式發現,第五個和第六個a滿足,因為是兩位字元,且滿足前面緊跟著兩個a(第三和第四個a)。所以匹配成功,輸出第五個和第六個a;
繼續向後尋找,從第七個a開始,程式發現,第七個和第八個a滿足,因為是兩位字元,且滿足前面緊跟著兩個a(第五和第六個a)。所以匹配成功,輸出第七和第八個a。尋找結束。

三、(?!)和(?<!)
從外觀上看,和前面一組很相似,區別就是把 ‘=’ 換成了 ‘!’
那麼意義剛好也是相反的。
[0-9a-z]{2}(?!aa)    意思是:匹配兩個字元,且後面緊跟著的不是aa
(?<=aa)[0-9a-z]{2}  意思是:匹配兩個字元,且前面緊跟著的不是aa
用法和前面講的差不多,這裡不再詳述。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.