正则表达式 捕获组,向前引用,零宽度断言,贪婪量词,惰性量词以及支配量词

捕获组
正则表达式中的括号相当于一个分组,比如下面这个正则表达式,就把字符串分成了"_"分割的三个分组,
然后可以利用$1引用第一个分组,$3引用第三个分组

Pattern p = Pattern.compile("([^_]+)_(\\d+)_([^_]+)");
String src1 = "孙燕姿_20091103_遇见.mp3";
Matcher m = p.matcher(src1);
System.out.println(m.replaceAll("$1_$3"));
//output 孙燕姿_遇见.mp3


向前引用
假设想匹配字符串中的"hello hello"这样的字串是很容易的,但是想匹配所有的这样的重复字符串呢(比如 "aaa aaa"和"www www")?
可以利用向前引用,即寻找已经匹配的捕获组.
比如下面这个正则表达式,寻找这样的匹配模式:多个字母(形成了捕获组1),一个空格,捕获组1

Pattern p = Pattern.compile("(\\w+)\\s\\1");
String src = "I always make make some mistakes when when writting.";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}
//output make make
when when


零宽度断言
假设想提取字符串中的括号中的数字(不包括括号),其实可以用这样的模式
(\d+)
但是这样会把括号也包括到匹配结果中,还要最后去掉括号.这时可以考虑零宽度断言,零宽度断言就像一种判断,
比如下面这个正则表达式,匹配的模式为: 多个数字,并且这些数字之前是"(",并且这些数字之后是")"

Pattern p = Pattern.compile("(?<=\\()\\d+(?=\\))");
String src = "some useless word (497872028) some other crap 1321112232";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}

// output 497872028

这个可以有如下变体.提取括号以及@@之间的 数字(不包括括号和@@)

Pattern p = Pattern.compile("(?<=\\(|@@)\\d+(?=\\)|@@)");
String src = "some @@497872027@@ useless word (497872028) some other crap 1321112232";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}

// output 497872027
// output 497872028


贪婪量词和惰性量词
考虑这个例子,提取##之间的部分(包括#),很容易想

Pattern p = Pattern.compile("#.+#");
String src = "some #stupid# word and some crap";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}
// #stupid#

那么字符串改一下呢

Pattern p = Pattern.compile("#.+#");
String src = "some #stupid# word and some #crap#";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}
// #stupid# word and some #crap#

因为默认情况下正则表达式的量词(即那个+)是贪婪的,它尝试尽可能多的匹配.
这时可以试下惰性量词,在+后面加?,表示尽可能少的进行匹配.

Pattern p = Pattern.compile("#.+?#");
String src = "some #stupid# word and some #crap#";
Matcher m = p.matcher(src);
while (m.find()) {
System.out.println(m.group());
}
// #stupid#
// #crap#

其实不用惰性量词,用 #[^#]+# 也可以

支配量词
支配量词比较复杂,我也不是完全清楚,这里谈一些个人见解,
我感觉它工作起来像贪婪量词,但是它不回溯,因为支配量词丢弃原来的状态.
比如贪婪量词模式 是\\w+:
字符串是 hello,那么这个字符串是肯定不满足模式的,因为它末尾没有那个分号.
不过贪婪量词先用 \\w+匹配 hello,发现不成功,进行回溯,就是用\\w+匹配hell,依然失败,接着匹配hel.
直到完全失败为止.

支配量词模式为\\w++:
在\\w++匹配了hello之后,发现后面没有分号,匹配失败,要进行回溯.但是支配量词没有保留h he hel hell这几个中间状态,
所以无法回溯,直接失败.
因此支配量词在这种情况下理论上效率会高一些,因为它少了这些回溯中间状态的步骤.
先看贪婪量词

Pattern p = Pattern.compile("\\w+:");
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= 40; i++) {
sb.append("alonglongsentence");
}

String src = sb.toString();
long start = System.currentTimeMillis();
for (int i = 0; i <= 100; i++) {
p.matcher(src).find();
}

System.out.println(System.currentTimeMillis() - start);
//1391

下面用支配量词

Pattern p = Pattern.compile("\\w++:");
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= 40; i++) {
sb.append("alonglongsentence");
}

String src = sb.toString();
long start = System.currentTimeMillis();
for (int i = 0; i <= 100; i++) {
p.matcher(src).find();
}

System.out.println(System.currentTimeMillis() - start);
// 875

上面都是匹配失败的情况,下面试试成功的.因为成功不需要回溯,因此速度快了很多嘛.为了对比,我把字符串长度和匹配次数都变大了.

Pattern p = Pattern.compile("\\w+:");
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= 400; i++) {
sb.append("alonglongsentence");
}
sb.append(":");
String src = sb.toString();
long start = System.currentTimeMillis();
for (int i = 0; i <= 1000; i++) {
p.matcher(src).find();
}

System.out.println(System.currentTimeMillis() - start);
// 250

Pattern p = Pattern.compile("\\w++:");
StringBuilder sb = new StringBuilder();
for (int i = 0; i <= 400; i++) {
sb.append("alonglongsentence");
}
sb.append(":");
String src = sb.toString();
long start = System.currentTimeMillis();
for (int i = 0; i <= 1000; i++) {
p.matcher(src).find();
}

System.out.println(System.currentTimeMillis() - start);
// 235

现在差别就不明显了.
关于贪婪量词和支配量词
我猜测字符串a12b 和模式a\d+c匹配过程如下
那个逗号表示匹配的位置

,a12b ,a\d+c
a,12b a,\d+c
a1,2b a,\d+c 记录回溯状态 a1,2b a\d+,c
a12,b a,\d+c 记录回溯状态 a12,b a\d+,c
b匹配\d失败,回溯状态为a12,b a\d+,c
b匹配c失败,回溯状态为a1,2b a\d+,c
2匹配c失败,没有状态可以回溯,匹配失败

换成支配量词
a12b a\d++c

,a12b ,a\d++c
a,12b a,\d++c
a1,2b a,\d++c 记录回溯状态a1,2b a\d++,c
a12,b a,\d++c 记录回溯状态a12,b a\d++,c
b匹配\d失败,回溯状态为a12,b a\d++,c 丢弃其他回溯状态
b匹配c失败,没有状态可以回溯,匹配失败

看出来支配量词回溯步骤变少了.a b之间的数字越多,它比贪婪量词要回溯的就越少

关于捕获组和零宽度断言,后来又补充了一部分内容[url]https://www.jianshu.com/p/31b13caa332c[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值