非捕获组(non-capturing): (?:X) (?=X) (?<=X) (?!X) (?
一、先从(?:)非捕获组说起
下面由一个例子引出非捕获组。
有两个金额:8899¥和 6688$。显然,前一个是 8899元的人民币,后一个是6688元的美元。我现在需要一个正则,要求提炼出它们的货币金额和货币种类。正则可以这写:(\\d)+([ ¥$])$ (在java中测试,所以多了转义字符'\')
测试程序如下:
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、姑且不理它们的名称,看下面的例子:
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,不满足正则的匹配条件,查找结束。
我们再延伸一下,刚说的情况的是 (?=)放在捕获的字符串后面,它如果放在前面又是什么结果呢?
例子换成:
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,作为独立的非捕获组
这些字都说的很抽象。不懂……。还是搜索下去。找到火龙果的解释如下:
以 (? 开头,)结尾的都称为非捕获组,在匹配完成后在内存中不保留匹配到的字符。
非捕获组的应用比较复杂,这里只能简单地说一下它们的意思。
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、(?
这两个与上面两个类似,上面两个是向后看,这个是向前看
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正则表达式的捕获组和非捕获组
比如有下面一段代码:
上面的代码意思是 和 不一定有,而且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=" aaa "
"+"ccc "
+" ddd "
+" eee "
+"fff ";
String regex="(.*?)";
Pattern pt=Pattern.compile(regex);
Matcher mt=pt.matcher(s);
while(mt.find())
{
System.out.println(mt.group(1).replaceAll("|","").trim());
}
}
}
解答二:使用非捕获组
package test1;
import java.util.regex.*;
public class Test6 {
public static void main(String[] args) {
String str = " aaa " +
" bbb
" +
"ccc " +
" ddd " +
" eee " +
"fff
";
String regex = "(?:/s*]*>)?(.*?)(?:/s*)?";
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"。在每个匹配的开头,所有捕获的输入都会被丢弃。
非捕获组
以 (?) 开头的组是纯的非捕获组,它不捕获文本,也不针对组合计进行计数。