正则表达式中的捕获组和非捕获组


捕获组 

语法: 

字符

描述

示例

(pattern)

匹配pattern并捕获结果,自动设置组号。

(abc)+d

匹配abcd或者abcabcd

(?<name>pattern)

(?'name'pattern)

匹配pattern并捕获结果,设置name为组名。

\num

对捕获组的反向引用。其中 num 是一个正整数。

(\w)(\w)\2\1

匹配abba

\k< name >

\k' name '

对命名捕获组的反向引用。其中 name 是捕获组名。

(?<group>\w)abc\k<group>

匹配xabcx

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个捕获组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。 
例如: 
(\d{4})-(\d{2}-(\d{2})) 
1 1 2 3 32 

也可以自己指定子表达式的组名。这样在表达式或程序中可以直接引用组名,当然也可以继续使用组号。但如果正则表达式中同时存在普通捕获组和命名捕获组,那么捕获组的编号就要特别注意,编号的规则是先对普通捕获组进行编号,再对命名捕获组进行编号。 
例如: 
(\d{4})-(?<date>\d{2}-(\d{2})) 
1 1 3 2 23 

非捕获组 
语法:

字符

描述

示例

(?:pattern)

匹配pattern,但不捕获匹配结果。

'industr(?:y|ies)'

匹配'industry'或'industries'。

(?=pattern)

零宽度正向预查,不捕获匹配结果。

'^(?:[1-9](?=[4-6])\d(?=[7-9])\d)$'

匹配 数字为: 147 即为第二个数字的范围为[4-6]

第三个数字的范围为[7-9]

不匹配 129  

(?!pattern)

零宽度负向预查,不捕获匹配结果。

'^(?:[1-9](?![4-6])\d(?![7-9])\d)$'

匹配 数字为:129 即 第二个数字的范围为[0-3]或者[7-9]  第三个数字的范围 [0-6]

不匹配 数字为 147

(?<=pattern)

零宽度正向回查,不捕获匹配结果。

'^\d{1,2}(?<=[3-9][7-9])$'

匹配 数字范围 >36


(?<!pattern)

零宽度负向回查,不捕获匹配结果。

'^\d{1,2}(?<!3[7-9])\d?(?<!36[1-9])$'

匹配 数字范围 为[0-360]

'^\d{1,2}(?<!3[7-9])$'

匹配 数字范围  <=36


非捕获组只匹配结果,但不捕获结果,也不会分配组号,当然也不能在表达式和程序中做进一步处理。 
首先(?:pattern)与(pattern)不同之处只是在于不捕获结果。 
接下来的四个非捕获组用于匹配pattern(或者不匹配pattern)位置之前(或之后)的内容。匹配的结果不包括pattern。 
例如: 
(?<=<(\w+)>).*(?=<\/\1>)匹配不包含属性的简单HTML标签内的内容。如:<div>hello</div>之中的hello,匹配结果不包括前缀<div>和后缀</div>。 

注释 
语法: 

字符

描述

示例

(?#comment)

comment是注释,不对正则表达式的处理产生任何影响

2[0-4]\d(?#200-249)|25[0-5](?#250-255)|1?\d\d?(?#0-199)

匹配0-255的整数

这个不解释了


匹配优先模式:通常, *?+, 和 {min,max}是匹配优先量词因为他们都是尽可能多地去匹配字符。

Hsuda:这里插播一个例子,对于匹配优先模式,我们将遇到一个很常见的陷阱。比如,我们要搜索的句子是

They call me "Hsudatalks" or "Freedom".

我希望搜索第一对引号里的内容,可能会产生这样的表达式".+",可是由于*是匹配优先的量词,我们得到的结果是

"Hsudatalks" or "Freedom"

这明显不是我们所期望的结果。那该怎么办呢?请看下面的帮助:

在他们后面使用问号则可以让他们尽可能少的匹配字符。那上面的例子我们采用".+?",所得到的便是"Hsudatalks"。

例:表达式<.+>所表示的意思是先搜索<,在匹配一个或者更多的任意字符,后面跟着一个>。在匹配时会出现 <em>text</em>,为了避免这种情况,我们使用<.+?> 使得表达式仅仅只会匹配标记<em>

顺序环视和逆序环视(?=...)(?!...)(?<=...) 和 (?<!...) 被成为环视,因为他们都是形容一个匹配条件而不消耗任何字符。比如:abc(?=.*xyz) 匹配右边的某个地方存在字符xyz的字符abc(如果不存在,便不能匹配)。 (?=...)被称为肯定顺序环视,因为他需要指定的字符在后面存在才能够完成匹配过程。相反的,(?!...)是否定顺序环视,因为他是在不存在指定字符的条件下的条件下完成匹配。相似的,(?<=...) 和(?<!...) 被称为肯定和否定的逆序环视,因为他们是在当前位置的左边寻找指定的字符存在或者不存在。逆序要比顺序更加局限,因为他不支持*,?和+这样的量词。

相关: 正则表达式由 RegExMatch()RegExReplace()SetTitleMatchMode支持。

最后:尽管这个页面介绍了正则表达式中最常用的元字符和一些特性,但是还有比如条件子表达式等相当一部分的其他特性您期望了解的。完整的PCRE手册请访问 www.pcre.org/pcre.txt 。(Hsuda:写到这里,发现AHK里的正则表达式原来用的是PCRE的库,关于PCRE,大家需要了解的是这是个地地道道的传统NFA引擎的正则库,所以,在编写正则表达式的时候是很考验您的优化功力的)




今天要写一个正则表达式,来做前端校验。主要部分是 用来匹配1~4095的数字。

 

现在就以这个为例,说一下表示范围的正则怎么写。

 

1、只有1~3位数字,即数字范围:1~999。

 

分析:第1个数字是1~9,后面的可以出现0~9的数字,最多2位。于是,就有了下面的字符串:

 

"[1-9]\\d{0,2}+"

 

2、4位数的,要小于等于4095,即数字范围:1000~4095。

 

分析:第1位,可以是1~4;第2位,依赖第1位,当第1位是4的时候,第2位只能是0;同理,第3位依赖第2位;第4位依赖第3位。所以,就有了以下代码:

 

"[1-4]\\d(?<!4[1-9])\\d\\d(?<!409[6-9])"

 

这里用的是非捕获(?<!X) X,通过零宽度的负 lookbehind 来实现的。

 

完整代码如下:

 

 

Java代码   收藏代码
  1. package com.lippeng.helloworld;  
  2.   
  3. import java.io.UnsupportedEncodingException;  
  4.   
  5. public class HelloWorld {  
  6.   
  7.     public static void main(String[] args) throws UnsupportedEncodingException {  
  8.   
  9.         // 1~4095  
  10.         String regex = "[1-9]\\d{0,2}+|[1-4]\\d(?<!4[1-9])\\d\\d(?<!409[6-9])";  
  11.   
  12.         String[] strArray = { "4094""4095""4096""5000""4000""900""10""9""0""1""-1""a""@" };  
  13.   
  14.         for (String str : strArray) {  
  15.             System.out.println(str + " " + str.matches(regex));  
  16.         }  
  17.   
  18.     }  
  19. }  
 

 

测试结果:

 

 

Java代码   收藏代码
  1. 4094 true  
  2. 4095 true  
  3. 4096 false  
  4. 5000 false  
  5. 4000 true  
  6. 900 true  
  7. 10 true  
  8. 9 true  
  9. 0 false  
  10. 1 true  
  11. -1 false  
  12. false  
  13. false  





 在 java api 文档中的正则表达式关于特殊构造(非捕获组)的说明看不懂。例如:

  1. (?:X)         X,作为非捕获组
  2. (?idmsux-idmsux)Nothing,但是将匹配标志由 on 转为 off
  3. (?idmsux-idmsux:X)X,作为带有给定标志 on - off 的非捕获组
  4. (?=X)X,通过零宽度的正 lookahead
  5. (?!X)X,通过零宽度的负 lookahead
  6. (?<=X)X,通过零宽度的正 lookbehind
  7. (?<!X)X,通过零宽度的负 lookbehind
  8. (?>X)X,作为独立的非捕获组

 

    这些字都说的很抽象。不懂……。还是搜索下去。找到 火龙果 的解释如下:以 (? 开头,) 结尾的都称为非捕获组,在匹配完成后在内存中不保留匹配到的字符。非捕获组的应用比较复杂,这里只能简单地说一下它们的意思。

1、(?:X) X,作为非捕获组

与捕获组 ( ) 的意思一样也是将其作为一组进行处理,与捕获组的区别在于不捕获匹配的文本,仅仅作为分组。

比如:要匹配 123123 这个,就可以写为 (123)\1 使用反向引用,这时只能用捕获组,在匹配 123 后会保留在内存中,便于反向引用,而 (?:123) 在匹配完后则不会保留,区别仅在于此。

2、(?idmsux-idmsux)  Nothing,但是将匹配标志i d m s u x on - off

用于标志匹配,比如:表达式 (?i)abc(?-i)def 这时,(?i) 打开不区分大小写开关,abc 匹配不区分大小地进行匹配,(?-i) 关闭标志,恢复不区分大小写,这时的 def 只能匹配 def

3、(?idmsux-idmsux:X)  X,作为带有给定标志 i d m s u x on - off

与上面的类似,上面的表达式,可以改写成为:(?i:abc)def,或者 (?i)abc(?-i:def)

4、(?=X) X,通过零宽度的正 lookahead

5、(?!X) X,通过零宽度的负 lookahead

(?=X) 表示当前位置(即字符的缝隙)后面允许出现的字符,比如:表示式 a(?=b),在字符串为 ab 时,可能匹配 a,后面的 (?=b) 表示,a 后面的缝隙,可以看作是零宽度。 (?!X) 表示当前位置后面不允许出现的字符

 6、(? <=X) X,通过零宽度的正 lookbehind

7、(? <!X) X,通过零宽度的负 lookbehind

这两个与上面两个类似,上面两个是向后看,这个是向前看

 8、(?>X) X,作为独立的非捕获组

匹配成功不进行回溯,这个比较复杂,也侵占量词“+”可以通用,比如:\d++ 可以写为 (?>\d+)。

 我认为,第1、2、3点比较好理解,4、5、6、7看类懂,还是用示例来说明:从“aacabab”找a,且后面只允许出现b。代码如下:

  1.  Pattern p = Pattern.compile("a(?=b)");  
  2. Matcher m = p.matcher("aacabab");  
  3. while(m.find()) {  
  4. System.out.println(m.group()+", start="+m.start()+", end="+m.end());  
  5. }  
  6. 运行结果:
  7.  a, start=3, end=4  
  8. a, start=5, end=6  

个人理解:在(?=b)这个“式”后面允许出现b,且这个“式”不占正则表达式位置(所谓0宽度),lookahead 的意思是b字符的前面,它前面紧接着是a,也就是a后面出现b。

8比较难理解,这个根据 火龙果 推荐的链接找到答案 http://www.regular-expressions.info/atomic.html

其中说的示例:来看 /\b(integer|insert|in)\b/ 匹配 integers 过程,第一个,当integer\b匹配到s时失败,然后字符串(integers)会回溯到i,再接着第二个(insert)去匹配。而把模式写成 /\b(?>integer|insert|in)\b/ 在刚才的第一个匹配失败,字符串(integers)不会回溯了,也不会有第二个去匹配了,所有速度会快一点点。

但是写 (?>X) 这种式子时要注意,是从左到右看的。/\b(?>integer|insert|in)\b/ ,与 /\b(?>in|integer|insert)\b/ 去匹配 insert,结果会不一样,前者可以匹配到,后者不能,什么原因自己分析下。一但匹配失败就会跳过,所以应该长的写在表达式前面.


 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值