正则表达式进阶

贪婪模式

*和+这两个限定符都可以标识匹配多个元素,在默认情况下它们会尽可能多的匹配文字,这就是所谓的“贪婪模式”

示例代码

###  测试代码
 @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());
        }
    }

###输出结果

<xml>helloworld<xml>

Process finished with exit code 0

注意观察上述代码,按照匹配规则,"<.>"表示匹配以‘<’开头,‘>’结尾,中间有任意个字符的字符串。那么很明显第一个<xml>和最后一个</xml>也是满足匹配规则的,但是结果只输出了整个字符串。这就是限定符的贪婪模式,会尽可能多的匹配字符,也就是说它匹配了“xml>helloworld<xml”这一整串内容。

怎么消除这种现象让它能匹配到第一个满足条件的“<xml>”和最后一个“</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());
        }
    }

### 输出结果
<xml>
<xml>

Process finished with exit code 0
    

将匹配的字符串从"<.>" 变更为 "<.?>",也就是在限定符后面加“?”即可消除贪婪模式

分组即“()”在正则表达式中的作用

使限定符可以作用于成对的匹配规则

  @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());
        }
    }
    
### 输出结果
abcabcabc

Process finished with exit code 0

可以看到,通过"()"将“abc”字符包裹之后,限定符"{3}"的修饰内容变成了“abc”整体出现三次,于是成功匹配了我们的目标字符串“abcabcabc”.

检索字符串

所谓检索字符串,可以理解为提取目标字符串,例如:对于字符串“<xml>helloworld<xml>”,通过检索可以提取出<xml></xml>标签的内容“helloworld”。

在理解检索之前,顺带提一下java中matcher类,我们常用的方法有2个

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

他们之间的区别在于:

matches()方法意为匹配,即目标字符串“整体”与我们的“正则表达式”进行匹配,返回结果是true或者false,这种情况通常用在例如:邮箱,电话号码等场景,用来检查输入的正确与否。

find()方法意为查找,也就是说它并不强调整体匹配是否正确,只是按照我们的“正则表达式”规则到目标字符串中查找是否有相匹配的字符串,其实就是部分匹配的意思。

示例代码

 @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());
//        }
    }
    
### 输出结果 false  这是因为"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());
        }
    }
    
### 输出结果 hello
这是因为在目标字符串"helloworld"中找到了与我们的匹配规则相符的字符串"hello"

find()方法的匹配是从左到右的,也就是说如果字符串中有多个满足表达式的子字符串,通过多次find可以全部找出。示例代码:

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

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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));
            }
        }
    }
    
###输出结果
0
matcher.group(0): hello
1
matcher.group(0): hello
    

以上代码第一次find找到了第一个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);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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): <xml>helloworld</xml>
matcher.group(1): helloworld
    

上面代码,通过给中间匹配加"([a-z])",matcher类会将这个分组的匹配结果放到group(1),称为捕获组1,通过matcher.group(1)即可以检索出标签内容"helloworld"。 记住group(0)永远是整个"正则表达式"的匹配结果,即捕获组0。在这个示例中,group(0)是"<xml>([a-z])</xml>"整串表达式的匹配结果。捕获组在group(几)取决于“()”在整个表达式中从左到右,从外到里的顺序(这个可以自行验证)

后向引用

在检索功能中我们知道了捕获组的概念,正则表达式后面的子表达式可以使用前面的捕获组,示例代码:

 @Test
    public void test() throws IOException {
        String str = "<xml>helloworld</xml>";
        
        
        //“\\1”(实际上是“\1”,第一个\是转义符号)用来引用捕获组1
        String regex = "<(xml)>.*</\\1>";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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): <xml>helloworld</xml>
matcher.group(1): xml

从上面代码应该很清楚 在正则表达式中可以通过"\i"的形式来引用捕获组i 的内容 即“xml“字符串。

注意事项:

1.这里为什么叫后向引用呢,这是因为“\i”所引用的捕获组i必须是在它前面所定义的,如果将上述表达式改为的"<\1>.*</xml>",就匹配不到了。

2.后向引用所引用的是"内容",而非"表达式"。示例代码:

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

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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));
            }
        }
    }

### 输出结果是空的

### 这是因为在匹配的过程中,“([a-z]{3})”首先匹配到了“xml”,于是后面“\\1”就变成了“xml”,于是整个表达式其实变成了 "<([a-z]{3})>.*</xml>",这显然是无法匹配到"<xml>helloworld</html>",所以整个表达式没有匹配结果。
    

断言

断言的概念比较模糊,我的理解是断言是特殊的限定符,用来对它所修饰的表达式添加匹配条件。示例代码:

 @Test
    public void test() throws IOException {
        String str = "hello2017";
        //“X1(?=X2)” 这是断言的一种写法,表示表达式X1匹配成功的前提是它后面有能够匹配表达式X2的内容。
        String regex = "hello(?=2017)";

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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)是整个表达式匹配的结果,但这里为什么不是hello2017而只输出了hello呢? 断言有点类似限定符,并不是匹配元素,所以整个表达式其实就是"hello",而断言给这个表达式匹配增加了条件。在这个例子中,hello所匹配成功的条件必须是后面跟着2017的hello,也就是只能是hello2017中的hello。将上述str改成"hello2018",虽然hello字符串依然能够配对hello,但是它后面跟的不是2017,不满足断言条件,因而匹配结果为空。

1.断言是不消耗字符的,在上面的例子中,虽然2017参与了"hello(?=2017)"这个表达式的运算,但它并没有被消耗,仍旧可以被后面的表达式所匹配。示例代码:

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

        Matcher matcher = Pattern.compile(regex).matcher(str);
        //必须先调用matcher.find(),这样才会将匹配的结果放到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): hello2

从这个结果可以看出,2017虽然参与了"hello"字符串的匹配,但是并没有被消耗掉,依然可以在后面的表达式"2"中使用,于是输出结果是hello2


2.断言不是捕获组,前面所述的"()"的使用中说过"()"的内容会被放入捕获组,那么代码中怎么区分是捕获组还是断言呢?其实就是?的作用,形如"(?=X)"由于加入了一个"?",因此代码执行时知道这是一个断言,不需要放入捕获组。

断言分为4种:

表达式 定义 说明
X1(?=X2) 零宽度正先行断言 仅当表达式X1后出现X2时,X1才能匹配成功。例如,“(hello)(?=\d)”匹配"hello1"成功,匹配"helloworld"不成功
X1(?!X2) 零宽度负先行断言 与上述相反。 例“(hello)(?!\d)” 匹配"hello1"不成功,匹配"helloworld"成功。
(?<=X2)X1 零宽度正后发断言 仅当表达式X1前面出现X2时,X1才能匹配成功。例如,“(?<=\d)(hello)”匹配"88hello"成功,而匹配"aahello"不成功
(?<!X) 零宽度负后发断言 仅当表达式X1前面出现X2时,X1才能匹配成功。例如,“(?<!\d)(hello)”匹配"ahello"成功,而匹配"88hello"不成功
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容