正則表達(dá)式進(jìn)階

貪婪模式

*和+這兩個(gè)限定符都可以標(biāo)識(shí)匹配多個(gè)元素,在默認(rèn)情況下它們會(huì)盡可能多的匹配文字,這就是所謂的“貪婪模式”

示例代碼

###  測(cè)試代碼
 @Test
    public void test() throws IOException {
        String str = "<xml>helloworld<xml>";
        String regex = "<.*>";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);

        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }

###輸出結(jié)果

<xml>helloworld<xml>

Process finished with exit code 0

注意觀察上述代碼,按照匹配規(guī)則,"<.>"表示匹配以‘<’開頭,‘>’結(jié)尾,中間有任意個(gè)字符的字符串。那么很明顯第一個(gè)<xml>和最后一個(gè)</xml>也是滿足匹配規(guī)則的,但是結(jié)果只輸出了整個(gè)字符串。這就是限定符的貪婪模式,會(huì)盡可能多的匹配字符,也就是說它匹配了“xml>helloworld<xml”這一整串內(nèi)容。

怎么消除這種現(xiàn)象讓它能匹配到第一個(gè)滿足條件的“<xml>”和最后一個(gè)“</xml>”呢?

示例代碼


    @Test
    public void test() throws IOException {
        String str = "<xml>helloworld<xml>";
        String regex = "<.*?>";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);

        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }

### 輸出結(jié)果
<xml>
<xml>

Process finished with exit code 0
    

將匹配的字符串從"<.>" 變更為 "<.?>",也就是在限定符后面加“?”即可消除貪婪模式

分組即“()”在正則表達(dá)式中的作用

使限定符可以作用于成對(duì)的匹配規(guī)則

  @Test
    public void test() throws IOException {
        String str = "abcabcabc";
        String regex = "(abc){3}";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);

        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
    
### 輸出結(jié)果
abcabcabc

Process finished with exit code 0

可以看到,通過"()"將“abc”字符包裹之后,限定符"{3}"的修飾內(nèi)容變成了“abc”整體出現(xiàn)三次,于是成功匹配了我們的目標(biāo)字符串“abcabcabc”.

檢索字符串

所謂檢索字符串,可以理解為提取目標(biāo)字符串,例如:對(duì)于字符串“<xml>helloworld<xml>”,通過檢索可以提取出<xml></xml>標(biāo)簽的內(nèi)容“helloworld”。

在理解檢索之前,順帶提一下java中matcher類,我們常用的方法有2個(gè)

matcher.matches();
matcher.find();  

他們之間的區(qū)別在于:

matches()方法意為匹配,即目標(biāo)字符串“整體”與我們的“正則表達(dá)式”進(jìn)行匹配,返回結(jié)果是true或者false,這種情況通常用在例如:郵箱,電話號(hào)碼等場(chǎng)景,用來檢查輸入的正確與否。

find()方法意為查找,也就是說它并不強(qiáng)調(diào)整體匹配是否正確,只是按照我們的“正則表達(dá)式”規(guī)則到目標(biāo)字符串中查找是否有相匹配的字符串,其實(shí)就是部分匹配的意思。

示例代碼

 @Test
    public void test() throws IOException {
        String str = "helloworld";
        String regex = "hello";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);

         System.out.print(matcher.matches());
//        while (matcher.find()) {
//            System.out.println(matcher.group());
//        }
    }
    
### 輸出結(jié)果 false  這是因?yàn)?hello"字符串與"helloworld"是不相匹配的

上述代碼更改為

 @Test
    public void test() throws IOException {
        String str = "helloworld";
        String regex = "hello";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(str);

//        System.out.print(matcher.matches());
        while (matcher.find()) {
            System.out.println(matcher.group());
        }
    }
    
### 輸出結(jié)果 hello
這是因?yàn)樵谀繕?biāo)字符串"helloworld"中找到了與我們的匹配規(guī)則相符的字符串"hello"

find()方法的匹配是從左到右的,也就是說如果字符串中有多個(gè)滿足表達(dá)式的子字符串,通過多次find可以全部找出。示例代碼:

 @Test
    public void test2() throws IOException {
        String str = "helloworldhello";
        String regex = "hello";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        int n = 0;
        while (matcher.find()) {
            System.out.println(n++);
            int count = matcher.groupCount();
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }
    
###輸出結(jié)果
0
matcher.group(0): hello
1
matcher.group(0): hello
    

以上代碼第一次find找到了第一個(gè)hello,第二次find找到了world后面的hello

看完上述代碼再來講下如何使用"()"的檢索功能

示例代碼

@Test
    public void test() throws IOException {
        String str = "<xml>helloworld</xml>";
        String regex = "<xml>([a-z]*)</xml>";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        if (matcher.find()) {
            int count = matcher.groupCount();
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }
    
###輸出結(jié)果

matcher.group(0): <xml>helloworld</xml>
matcher.group(1): helloworld
    

上面代碼,通過給中間匹配加"([a-z])",matcher類會(huì)將這個(gè)分組的匹配結(jié)果放到group(1),稱為捕獲組1,通過matcher.group(1)即可以檢索出標(biāo)簽內(nèi)容"helloworld"。 記住group(0)永遠(yuǎn)是整個(gè)"正則表達(dá)式"的匹配結(jié)果,即捕獲組0。在這個(gè)示例中,group(0)是"<xml>([a-z])</xml>"整串表達(dá)式的匹配結(jié)果。捕獲組在group(幾)取決于“()”在整個(gè)表達(dá)式中從左到右,從外到里的順序(這個(gè)可以自行驗(yàn)證)

后向引用

在檢索功能中我們知道了捕獲組的概念,正則表達(dá)式后面的子表達(dá)式可以使用前面的捕獲組,示例代碼:

 @Test
    public void test() throws IOException {
        String str = "<xml>helloworld</xml>";
        
        
        //“\\1”(實(shí)際上是“\1”,第一個(gè)\是轉(zhuǎn)義符號(hào))用來引用捕獲組1
        String regex = "<(xml)>.*</\\1>";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        if (matcher.find()) {
            int count = matcher.groupCount();
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }
    
    
### 輸出結(jié)果

matcher.group(0): <xml>helloworld</xml>
matcher.group(1): xml

從上面代碼應(yīng)該很清楚 在正則表達(dá)式中可以通過"\i"的形式來引用捕獲組i 的內(nèi)容 即“xml“字符串。

注意事項(xiàng):

1.這里為什么叫后向引用呢,這是因?yàn)椤癨i”所引用的捕獲組i必須是在它前面所定義的,如果將上述表達(dá)式改為的"<\1>.*</xml>",就匹配不到了。

2.后向引用所引用的是"內(nèi)容",而非"表達(dá)式"。示例代碼:

將上面例子中的代碼改為:
 @Test
    public void test() throws IOException {
        String str = "<xml>helloworld</html>";
        String regex = "<([a-z]{3})>.*</\\1>";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        if (matcher.find()) {
            int count = matcher.groupCount();
            System.out.println(count);
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }

### 輸出結(jié)果是空的

### 這是因?yàn)樵谄ヅ涞倪^程中,“([a-z]{3})”首先匹配到了“xml”,于是后面“\\1”就變成了“xml”,于是整個(gè)表達(dá)式其實(shí)變成了 "<([a-z]{3})>.*</xml>",這顯然是無法匹配到"<xml>helloworld</html>",所以整個(gè)表達(dá)式?jīng)]有匹配結(jié)果。
    

斷言

斷言的概念比較模糊,我的理解是斷言是特殊的限定符,用來對(duì)它所修飾的表達(dá)式添加匹配條件。示例代碼:

 @Test
    public void test() throws IOException {
        String str = "hello2017";
        //“X1(?=X2)” 這是斷言的一種寫法,表示表達(dá)式X1匹配成功的前提是它后面有能夠匹配表達(dá)式X2的內(nèi)容。
        String regex = "hello(?=2017)";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        if (matcher.find()) {
            int count = matcher.groupCount();
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }
    
### 輸出
matcher.group(0): hello

Process finished with exit code 0

我們前面說過group(0)是整個(gè)表達(dá)式匹配的結(jié)果,但這里為什么不是hello2017而只輸出了hello呢? 斷言有點(diǎn)類似限定符,并不是匹配元素,所以整個(gè)表達(dá)式其實(shí)就是"hello",而斷言給這個(gè)表達(dá)式匹配增加了條件。在這個(gè)例子中,hello所匹配成功的條件必須是后面跟著2017的hello,也就是只能是hello2017中的hello。將上述str改成"hello2018",雖然hello字符串依然能夠配對(duì)hello,但是它后面跟的不是2017,不滿足斷言條件,因而匹配結(jié)果為空。

1.斷言是不消耗字符的,在上面的例子中,雖然2017參與了"hello(?=2017)"這個(gè)表達(dá)式的運(yùn)算,但它并沒有被消耗,仍舊可以被后面的表達(dá)式所匹配。示例代碼:

 @Test
    public void test() throws IOException {
        String str = "hello2017";
        String regex = "hello(?=2017)2";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必須先調(diào)用matcher.find(),這樣才會(huì)將匹配的結(jié)果放到group中
        if (matcher.find()) {
            int count = matcher.groupCount();
            for (int i = 0; i <= count; i++) {
                System.out.println("matcher.group(" + i + "): " + matcher.group(i));
            }
        }
    }
    
###輸出結(jié)果
matcher.group(0): hello2

從這個(gè)結(jié)果可以看出,2017雖然參與了"hello"字符串的匹配,但是并沒有被消耗掉,依然可以在后面的表達(dá)式"2"中使用,于是輸出結(jié)果是hello2


2.斷言不是捕獲組,前面所述的"()"的使用中說過"()"的內(nèi)容會(huì)被放入捕獲組,那么代碼中怎么區(qū)分是捕獲組還是斷言呢?其實(shí)就是?的作用,形如"(?=X)"由于加入了一個(gè)"?",因此代碼執(zhí)行時(shí)知道這是一個(gè)斷言,不需要放入捕獲組。

斷言分為4種:

表達(dá)式 定義 說明
X1(?=X2) 零寬度正先行斷言 僅當(dāng)表達(dá)式X1后出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(hello)(?=\d)”匹配"hello1"成功,匹配"helloworld"不成功
X1(?!X2) 零寬度負(fù)先行斷言 與上述相反。 例“(hello)(?!\d)” 匹配"hello1"不成功,匹配"helloworld"成功。
(?<=X2)X1 零寬度正后發(fā)斷言 僅當(dāng)表達(dá)式X1前面出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(?<=\d)(hello)”匹配"88hello"成功,而匹配"aahello"不成功
(?<!X) 零寬度負(fù)后發(fā)斷言 僅當(dāng)表達(dá)式X1前面出現(xiàn)X2時(shí),X1才能匹配成功。例如,“(?<!\d)(hello)”匹配"ahello"成功,而匹配"88hello"不成功
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 正則表達(dá)式到底是什么東西?字符是計(jì)算機(jī)軟件處理文字時(shí)最基本的單位,可能是字母,數(shù)字,標(biāo)點(diǎn)符號(hào),空格,換行符,漢字等...
    獅子挽歌閱讀 2,287評(píng)論 0 9
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,563評(píng)論 19 139
  • 圖形化顯示正則 RegExp對(duì)象 修飾符 g:global 全文搜索,不添加,搜索到第一個(gè)匹配停止i:ign...
    靜候那一米陽光閱讀 591評(píng)論 1 2
  • 注:本篇文章只為方便查看,特此保留,如有冒犯,敬請(qǐng)諒解?。?! 本文目標(biāo) 30分鐘內(nèi)讓你明白正則表達(dá)式是什么,并對(duì)它...
    阿杰Alex閱讀 1,563評(píng)論 0 10

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