zoukankan      html  css  js  c++  java
  • java正则表达非捕获组详解

    这几天看了下正则表达式,对非捕获组(non-capturing)进行下总结。

    主要总结 1个 + 2组  一共5个。
    (?:X) (?=X) (?<=X) (?!X) (?<!X)

    这四个非捕获组用于匹配表达式X,但是不包含表达式的文本。

    (?=X ) 零宽度正先行断言。仅当子表达式 X 在 此位置的右侧匹配时才继续匹配。例如,/w+(?=/d) 与后跟数字的单词匹配,而不与该数字匹配。此构造不会回溯。
    (?!X) 零宽度负先行断言。仅当子表达式 X 不在 此位置的右侧匹配时才继续匹配。例如,例如,/w+(?!/d) 与后不跟数字的单词匹配,而不与该数字匹配
    (?<=X) 零宽度正后发断言。仅当子表达式 X 在 此位置的左侧匹配时才继续匹配。例如,(?<=19)99 与跟在 19 后面的 99 的实例匹配。此构造不会回溯。
    (?<!X) 零宽度负后发断言。仅当子表达式 X 不在此位置的左侧匹配时才继续匹配。例如,(?<!19)99 与不跟在 19 后面的 99 的实例匹配

    一、先从(?:)非捕获组说起。
    下面由一个例子引出非捕获组。

    有两个金额:8899¥ 和 6688$ 。显然,前一个是8899元的人民币,后一个是6688元的美元。我现在需要一个正则,要求提炼出它们的货币金额和货币种类。正则可以这写:(\\d)+([¥$])$  (在java中测试,所以多了转义字符'\')
    测试程序如下:

         Pattern p = Pattern.compile("(\\d+)([¥$])$");  
         String str = "8899¥";  
         Matcher m = p.matcher(str);  
         if(m.matches()){  
          System.out.println("货币金额: " + m.group(1));  
          System.out.println("货币种类: " + m.group(2));  
         }  

    输出结果为:

    货币金额: 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、姑且不理它们的名称,看下面的例子:

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

    这段程序输出32 38

    这个正则的意思是:匹配这么一个字符串,它要满足:是两位字符(数字,或字母),且后面紧跟着两个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,不满足正则的匹配条件,查找结束。
    我们再延伸一下,刚说的情况的是(?=)放在捕获的字符串后面,它如果放在前面又是什么结果呢?
    例子换成:

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

    它的输出是hope。

    正则的意思是:是否能匹配hopeful,如果能,则捕获hopeful中的hope。当然继续向后查找匹配的子串,是从f开始。
    比较一下可以看出,(?=hopeful)hope 和 hope(?=ful),两个正则的效果其实是一样的。

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

    这个正则的意思是:匹配这么一个字符串,它要满足:是两位字符(数字或字母),且前面紧跟的是两个字母 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
    用法和前面讲的差不多,这里不再详述。

  • 相关阅读:
    mybatis入门_一对多,多对多映射以及整合spring框架
    mybatis入门_配置文件的配置
    mybatis入门_mybatis基本原理以及入门程序
    BOM 3.1 location对象 | history对象 | navigator对象 | 定时器 | 三大系列
    Sublime Text Build 3207 x64 无法安装Package Control和插件
    无法启动此程序,因为计算机中丢失api-ms-win-crt-runtime-l1-1-0.dll已解决
    测试
    ECMAScript1.4 对象 | 简单数据类型与复杂数据类型 | 内置对象 | 基本包装类型 | String
    ECMAScript1.3 数组 | 函数 | 作用域 | 预解析
    webAPI(DOM) 2.1 获取页面元素 | 事件1 | 属性操作 | 节点 | 创建元素 | 事件2
  • 原文地址:https://www.cnblogs.com/aduck/p/2914709.html
Copyright © 2011-2022 走看看