文章目录
- 一、Greediness(贪婪型)
- (一)贪婪模式示例
- (二)贪婪模式的匹配过程(贪婪模式的回溯)
- 二、Reluctant(勉强型)
- (一)非贪婪模式示例
- (二)非贪婪模式的匹配过程
- 三、Possessive(占有型)
- (一)占有模式示例
- (二)占用模式的匹配过程
一、Greediness(贪婪型)
贪婪模式,最大匹配,匹配优先。
在 Greediness
的模式下,会尽量大范围的匹配,直到匹配了整个内容,这时发现匹配不能成功时,开始回退缩小匹配范围,直到匹配成功。
默认情况下,所有的限定符都是贪婪模式,表示尽可能多的去捕获字符。
(一)贪婪模式示例
正则表达式:/<.+>/
,这个正则表达式的含义:匹配以 <
为首,以 >
为尾,中间是 1
个或多个任意字符的字符串。
被查找的字符串:a<tr>aava </tr>abb
匹配的结果:<tr>aava </tr>
Java 代码举例如下:
String test = "a<tr>aava </tr>abb";
String reg = "<.+>";
System.out.println(test.replaceAll(reg, "###"));
说明:上面的 Java 代码会将字符串 a<tr>aava </tr>abb
中所有符合正则式的字符串替换为 ###
,最后输出结果为:a###abb
。其实 <tr>
和</tr>
也符合正则式 /<.+>/
,但是贪婪呀,因为被匹配的字符串还很长,所以会继续匹配下去,直到捕获到最多且匹配成功为止。
注意:正则表达式的限定符(量词)?
、+
、*
以及区间限定符 {n,m}
默认都是贪婪模式。
再举个例子:
String test = "foooood";
String reg ="o{1,}";
System.out.println(test.replaceAll(reg, "#"));
上面的 Java 代码会一次匹配全部的字符 o
,什么意思?就是说整个正则表达式单次匹配成功中,会匹配尽可能多的字符,而 ooooo
是符合正则式 o{1,}
的,所以在单次匹配成功中就全部捕获了,所以最后输出的结果为:f#d
。如果你想得到这样的结果:f#####d
,应该怎么写表达式呢?那么表达式成功匹配一个 o
时,就算整个正则表达式匹配成功 1 次,那么成功匹配 5 个 o
,就算整个正则式匹配成功了 5 次,这样就会执行 5 次替换,最后 ooooo
就会替换成 #####
,我们看下面的示例。
String test = "foooood";
String reg ="o{1,}?";
System.out.println(test.replaceAll(reg, "#"));
在区间量词后添加个问号 ?
,就是非贪婪模式,那么匹配了一个 o
,就算正则式成功匹配一次,然后正则式又重新从正则式的第一个子表达式对被匹配的字符串已经成功匹配的下个字符开始继续匹配,直到无字符可匹配为止。
(二)贪婪模式的匹配过程(贪婪模式的回溯)
贪婪模式的正则表达式<.*>去匹配字符串"a<>aava</>abb",会匹配到"<>aava</>",下面说一下贪婪模式的具体匹配过程。
首先 <.*>
中的 <
获得控制权,尝试匹配字符串"a<>aava</>abb"中的第一个字符"a",匹配失败。继续尝试匹配第二个字符"<",匹配成功,将控制权交给 .*
。
.*
为贪婪模式,会尽可能多的匹配字符,会先去匹配第三个字符">",匹配成功,记录下一个回溯状态,继续匹配第四个字符"a",直到匹配到字符串结尾的最后一个字符"b"。字符串后没有字符了,.*
继续匹配失败,将控制权交给 >
。
>
尝试匹配字符串结尾失败,向前查找可回溯状态,控制权交给 .*
,.*
让出一个字符,也就是字符串结尾的"b",然后将控制权交给 >
。>
尝试匹配字符串结尾的"b",匹配失败,再次向前查找可回溯状态,将控制权交给 .*
…
重复以上过程,直到 .*
让出第十个字符">",>
匹配第十个字符">"成功,从而整个正则表达式匹配成功一次。<
继续从第十一个字符"a"开始匹配,但是直到字符串结束都没有匹配成功的,最终匹配结束。
可以看到,.*
会尽可能多的匹配字符,直到无法继续匹配,才会将控制权交给接下来的表达式。当接下来的表达式匹配失败后,.*
会让出之前匹配的字符,直到整个正则表达式匹配成功。
总结:贪婪模式的回溯,会让出已经匹配成功的字符给下一个正则式的表达式。吃太多了,别人没得吃,一点点吐出去给别人吃~
二、Reluctant(勉强型)
也叫 Laziness
,懒惰型,懒惰模式,勉强模式,非贪婪模式,最小匹配,忽略优先。
在 Reluctant
的模式下,就是在匹配成功的前提下,尽可能少的匹配字符。
在限定词后增加 ?
,则是非贪婪模式,表示尽可能少的去捕获字符。
例如:??
,*?
,+?
,{n,}?
,{n,m}?
(一)非贪婪模式示例
被查找的字符串:a<tr>aava </tr>abb
你希望匹配结果为:<tr>
,就要使用表达式:/<.+?>/
,但是从被查找的字符串可以知道,有两个地方匹配正则表达式,所以最终会匹配得到两个结果:<tr>
和 </tr>
。
Java 代码举例如下:
String test = "a<tr>aava </tr>abb";
String reg = "<.+>";
System.out.println(test.replaceAll(reg, "###"));
上面的 Java 代码会将字符串 a<tr>aava </tr>abb
中所有符合表达式的字符串替换为 ###
,最后输出结果为:a###aava ###abb
。
(二)非贪婪模式的匹配过程
先举个例子:
正则式 <.+?>
去匹配字符串 a<>aava</>abb
,会成功匹配到谁呢?四个选项:
A.<>
B.</>
C.<>aava</>
D.匹配失败
我相信很多人会选中选项 B,不过正确答案是 C。
想要理解为什么在非贪婪模式下,<.+?>
还是匹配了这么多字符,需要知道非贪婪模式下的匹配过程。
首先 <.+?>
中的 <
获得控制权,匹配字符串中的第一个字符"a",匹配失败。<
继续尝试匹配第二个字符"<",匹配成功。
接下来 .+?
获得控制权,.
可以匹配除换行符之外的任意一个字符,+
表示一个或多个,?
表示非贪婪模式,即在成功的前提下尽量少的匹配字符,所以 .+?
会先去匹配成功一次,然后立刻就会将控制权交给正则表达式中的下一个。如果是贪婪模式,.+
是不会匹配成功就早早地让出控制权的,而是会继续匹配下去,直到匹配失败了才把控制权让给正则表达式的下一个字符。
所以 .+?
获得控制权后,会先去匹配第三个字符">",成功后记录下回溯状态,立即将控制权交给表达式中的 >
。>
获得控制权后,会尝试匹配第四个字符"a",匹配失败,向前查找可回溯状态,于是 .+?
重新取得控制权,.+?
记录的回溯位置是第 3 个字符“>”,于是 .+?
会去匹配回溯位置的下一个字符,也就是第 4 个字符“a”,匹配成功后再次记录下回溯状态,然后再次将控制权交给 >
。>
获得控制权后,会尝试匹配第五个字符"a",结果匹配失败,再次向前查找可回溯状态…
就这样重复上述过程,直到 .+?
匹配到了第九个字符"/",将控制权交给 >
。>
匹配第十个字符">",终于匹配成功了,从而整个正则表达式匹配成功一次,记录下匹配结果。因为每个字符只能被正则匹配一次,所以 <
继续从第十一个字符"a"开始匹配,但是直到字符串结束都没有匹配成功的,最终匹配结束。
可以看到 .+?
会先尽可能少的匹配字符,优先将控制权交给接下来的表达式。但因为接下来的表达式一直匹配失败,.+?
不得不继续匹配字符。最终 .+?
的匹配内容是">aava</",并且进行了 6
次回溯。
总结:非贪婪模式的回溯,是被迫继续匹配下一个字符,再让出控制权。给别人吃,别人吃不了,只好自己吃掉。
三、Possessive(占有型)
占有模式,完全匹配模式。
在限定符后面添加 +
就成为“占有模式”,例如:?+
、*+
、++
、{n,}+
、{n,m}+
Possessive
模式与 Greediness
有一定的相似性,那就是都尽量匹配最大范围的内容,直到内容结束,但与 Greediness
不同的是,完全匹配不再回退尝试匹配更小的范围,也就是“不回溯”。
(一)占有模式示例
String test = "a<tr>aava </tr>abb";
String test2 = "<tr>";
String reg = "<.++>";
System.out.println(test.replaceAll(reg, "###"));
System.out.println(test2.replaceAll(reg, "###"));
以上的代码,输出结果为:a<tr>aava </tr>abb
和 <tr>
,也就是说字符串a<tr>aava </tr>abb
和 <tr>
都不符合正则表达式 <.++>
,那么这个表达式到底是怎么匹配的呢?
(二)占用模式的匹配过程
接下来看不回溯的占有模式是怎么匹配的。
占有模式的正则表达式 <.*+>
去匹配字符串"a<>aava</>abb",会匹配失败。具体的匹配过程如下。
前两步与贪婪模式的 <.*>
匹配过程基本一致。
<
获得控制权,尝试匹配字符串"a<>aava</>abb"中的第一个字符"a",匹配失败。继续尝试匹配第二个字符"<",匹配成功,将控制权交给 .*+
。
.*+
也会尽可能多的匹配字符。不同的是,占有模式下表达式并不会记录回溯状态。.*+
匹配到字符串结尾字符"b"后,没有字符了,于是继续匹配失败,将控制权交给 >
。
>
获得控制权后,尝试匹配字符串结尾,结尾无字符,匹配失败。因为没有回溯状态可查找,>
直接匹配失败,从而整个正则表达式 <.*+>
匹配失败。
带区间的类似 .{m,n}+
也是不带回溯状态的,需要注意,具体匹配过程类似上文,就不详述了。
总结:把东西全部吃掉了,也不吐出来,结果导致别人没得吃