java正则表达式非捕获组详解

转载地址:http://blog.csdn.net/bingjie1217/article/details/15500069

 

非捕获组(non-capturing) (?:X) (?=X) (?<=X) (?!X) (?<!X)
一、先从(?:)非捕获组说起
下面由一个例子引出非捕获组。

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

  1. package test;  
  2.   
  3. import java.util.regex.Matcher;  
  4. import java.util.regex.Pattern;  
  5.   
  6. public class TestReg {  
  7.     public static void main(String[] args) {  
  8.   
  9.         Pattern p = Pattern.compile("^(\\d+)([¥$])$");  
  10.         String str = "8899¥";  
  11.         Matcher m = p.matcher(str);  
  12.         if (m.matches()) {  
  13.             System.out.println("货币金额: " + m.group(1));  
  14.             System.out.println("货币种类: " + m.group(2));  
  15.         }  
  16.     }  
  17. }  
package test;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class TestReg {
	public static void main(String[] args) {

		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¥。
那么正则如下:
(\\d+)(\\.?)(\\d+)([$])$
里用括号分了四组,所以要输出货币金额的整数部分和货币种类,要分别输了group(1),group(4)了。如果输出部分和正则是分开的,我希望只修改正则而不去修改输出部分的代码,也就是还是用group(1)group(2)作为输出。由此可以引出非捕获组(?:)
把前面的正则修改为:
(\\d+)(?:\\.?)(?:\\d+)([$])$
这样,还是用group(1),group(2)做为输出,同样输出了 8899
这个正则的中间两个组用到的就是非捕获组(?:),它可以理解为只分组而不捕获。

 

二、(?=)(?<=)
有的资料把它们叫做肯定式向前查找和肯定式向后查找;
有的资料也叫做肯定顺序环视和肯定逆序环视。

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

  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. }   
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
这个正则的意思是:匹配这么一个字符串,它要满足:是两位字符(数字,或字母),且[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
分析一下:
这个字符串一共有8a
第一次匹配比较容易找到,那就是前四个:aaaa ,当然第三和第四个 a是不捕获的,所以输出是第一和第二个a
接着继续查找,这时是从第三个a开始,三到六,这4a区配到了,所以输出第三和第四个a
接着继续查找,这时是从第五个a开始,五到八,这4a区配到了,所以输出第五和第六个a
接着往后查找,这时是从第七个a开始,显然,第七和第八个a,不满足正则的匹配条件,查找结束。
我们再延伸一下,刚说的情况的是(?=)放在捕获的字符串后面,它如果放在前面又是什么结果呢?
例子换成:

[java]view plaincopy

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

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

 

java正则表达式非捕获组

javaapi 文档中的正则表达式关于特殊构造(非捕获组)的说明看不懂。例如:(?:X) X,作为非捕获组
(?idmsux-idmsux) Nothing
,但是将匹配标志由 on转为 off
(?idmsux-idmsux:X) X
,作为带有给定标志 on - off的非捕获组
(?=X) X
,通过零宽度的正lookahead
(?!X) X
,通过零宽度的负lookahead
(?<=X) X
,通过零宽度的正lookbehind
(?<!X) X
,通过零宽度的负lookbehind
(?>X) X
,作为独立的非捕获组

这些字都说的很抽象。不懂……。还是搜索下去。找到火龙果的解释如下:

(? 开头,)结尾的都称为非捕获组,在匹配完成后在内存中不保留匹配到的字符。

非捕获组的应用比较复杂,这里只能简单地说一下它们的意思。

1(?:X) X,作为非捕获组
与捕获组 ( ) 的意思一样也是将其作为一组进行处理,与捕获组的区别在于不捕获匹配的文本,
仅仅作为分组。
比如:要匹配 123123 这个,就可以写为 (123)\1 使用反向引用,这时只能用捕获组,在匹配
123
后会保留在内存中,便于反向引用,而 (?:123)在匹配完后则不会保留,区别仅在于此。

2(?idmsux-idmsux)Nothing,但是将匹配标志i d m su x on - off
用于标志匹配,比如:表达式 (?i)abc(?-i)def这时,(?i) 打开不区分大小写开关,abc 匹配
不区分大小地进行匹配,(?-i)关闭标志,恢复不区分大小写,这时的 def只能匹配 def

3(?idmsux-idmsux:X)X,作为带有给定标志 i d ms 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+)

我认为,第123点比较好理解,4567看类懂,还是用示例来说明:从“aacabab”a,且后面只允许出现b。代码如下:
Pattern p = Pattern.compile("a(?=b)");
Matcher m = p.matcher("aacabab");
while(m.find()) {
System.out.println(m.group()+", start="+m.start()+",end="+m.end());
}

运行结果:
a, start=3, end=4
a, start=5, end=6

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

8比较难理解

其中说的示例:来看 /\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,结果会不一样,前者可以匹配到,后者不能,什么原因自己分析下。一但匹配失败就会跳过,所以应该长的写在表达式前面。

 

复习下Java正则表达式的捕获组和非捕获组

比如有下面一段代码:
<a href="11"> <font color="21">aaa </font></a>
<a href="12"> <font color="22">bbb</font> </a>
<a href="13">ccc </a>
<a href="14"> <font color="24">ddd</font> </a>
<a href="15"> <font color="25">eee</font> </a>
<a href="16">fff </a>

上面的代码意思是 <font color="***"> </font>不一定有,而且color的值也可能不一样
我现在想得到
aaa
bbb
ccc
ddd
eee
fff

这个正则表达式该怎么写?谢谢

我的解答:(未使用非捕获组,借用了String方法的replaceAll

package test1;
import java.util.regex.*;
public class Test6
{
public static void main(String[] args)
{
String s="<a href="11"> <fontcolor="21">aaa </font> </a> "
+"<a href="12"> <font color="22">bbb</font> </a> "
+"<a href="13">ccc </a> "
+"<a href="14"> <font color="23">ddd</font> </a>"
+"<a href="15"> <font color="25">eee</font> </a> "
+"<a href="16">fff </a> ";
String regex="<a.*?>(.*?)</a>";

Pattern pt=Pattern.compile(regex);
Matcher mt=pt.matcher(s);
while(mt.find())
{
System.out.println(mt.group(1).replaceAll("<font.*?>|</font>","").trim());
}
}
}

解答二:使用非捕获组

package test1;
import java.util.regex.*;

public class Test6 {
public static void main(String[] args) {
String str = "<a href="11"> <fontcolor="21">aaa </font> </a>" +
"<a href="12"> <fontcolor="22">bbb </font> </a>" +
"<a href="13">ccc </a> " +
"<a href="14"> <fontcolor="23">ddd </font> </a>" +
"<a href="15"> <fontcolor="25">eee </font> </a> " +
"<a href="16">fff </a> ";
String regex = "<a.*?>(?:/s*<font[^>]*>)?(.*?)(?:</font>/s*)?</a>";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(str);
while(matcher.find()) {
System.out.println(matcher.group(1));
}

}

}

总结:什么是非捕获组、什么是捕获组. ----------引自帮助文档

组和捕获

捕获组可以通过从左到右计算其开括号来编号。例如,在表达式 ((A)(B(C)))中,存在四个这样的组:

1

((A)(B(C)))

2

/A

3

(B(C))

4

(C)

组零始终代表整个表达式。

之所以这样命名捕获组是因为在匹配中,保存了与这些组匹配的输入序列的每个子序列。捕获的子序列稍后可以通过 Back引用在表达式中使用,
也可以在匹配操作完成后从匹配器获取。

与组关联的捕获输入始终是与组最近匹配的子序列。如果由于量化的缘故再次计算了组,则在第二次计算失败时将保留其以前捕获的值(如果有的话)
例如,将字符串 "aba" 与表达式 (a(b)?)+ 相匹配,会将第二组设置为 "b"。在每个匹配的开头,所有捕获的输入都会被丢弃。

非捕获组

(?) 开头的组是纯的非捕获组,它不捕获文本,也不针对组合计进行计数。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值