您的位置:首页 > 编程语言 > Java开发

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

2013-12-21 10:12 211 查看
转载地址:http://blog.csdn.net/bingjie1217/article/details/15500069



非捕获组(non-capturing): (?:X) (?=X) (?<=X) (?!X) (?<!X)

一、先从(?:)非捕获组说起

下面由一个例子引出非捕获组。

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

测试程序如下:

[java]
view plaincopyprint?

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));
}
}
}

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

[java]
view plaincopyprint?

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());
}

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

分析一下:

这个字符串一共有8个a。

第一次匹配比较容易找到,那就是前四个:aaaa ,当然第三和第四个 a是不捕获的,所以输出是第一和第二个a;

接着继续查找,这时是从第三个a开始,三到六,这4个a区配到了,所以输出第三和第四个a;

接着继续查找,这时是从第五个a开始,五到八,这4个a区配到了,所以输出第五和第六个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+)。

我认为,第1、2、3点比较好理解,4、5、6、7看类懂,还是用示例来说明:从“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"。在每个匹配的开头,所有捕获的输入都会被丢弃。

非捕获组

以 (?) 开头的组是纯的非捕获组,它不捕获文本,也不针对组合计进行计数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: