正则表达式的固化分组

固化分组,又叫原子组。

语法格式:(?>…)

我们在使用非贪婪模式时,匹配过程中可能会进行多次的回溯,回溯越多,正则表达式的运行效率就越低。而固化分组就是用来减少回溯次数的。

实际上,固化分组 (?>…) 的匹配与正常的匹配并无分别,它并不会改变匹配结果。唯一的不同就是:固化分组匹配结束时,它匹配到的文本已经固化为一个单元,只能作为整体而保留或放弃,小括号内的子表达式中未使用的备用状态都会被放弃,所以回溯永远也不能选择其中的状态(因此不能参与回溯)。

javaScriptjavapython 中并不支持固化分组的语法。不过,它在 php.NET 中表现良好。

下面我们通过一个 php 的固化分组正则表达式例子来深入地理解固化分组:

假如要处理一批数据,原格式为 123.456, 因为浮点数显示问题,部分数据格式会变为 123.456000000789 这种, 现要求只保留小数点后 2~3 位,但是最后一位不能为 0,那么这个正则怎么写呢?

$str = "123.456000000789";
echo preg_replace("/(\.\d\d[1-9]?)\d*/","\\1",$str); // 123.456

以上的正则表达式,对于 123.456 这种格式的数据,将白白处理一遍,为了提高效率,我们应该将 123.456 排除在外,我们将量词 * 改成 +。如下所示:

$str = "123.456";
echo preg_replace("/(\.\d\d[1-9]?)\d+/","\\1",$str);

(.\d\d[1-9]?) 会正常匹配到 .456,而 \d+ 表示匹配大于等于 1 个由 0-9 的任意数字组成的字符串,即匹配至少一个任意数字,所以 \d+ 匹配到 6 后面的结尾处位置时,会匹配失败,然后会要求 [1-9]? 回溯到备选状态,也就是放弃匹配数字 6,让给 \d+ 去匹配,结果匹配成功,最后整个正则式匹配到字符串 .456(.\d\d[1-9]?) 匹配到 .45\d+ 匹配到 6,所以 \1 引用到的是 .45,也就是把源字符串 123.456,后面的 .456 替换成了 .45,本来保留小数点后 3 位是符合需求的,现在反而被处理成了 .45,显然 123.45 不是我们期望的匹配结果,那我们应该怎么做呢?能否让 [1-9]? 一旦匹配成功,便不再进行回溯,这里就要用到固化分组,如下所示:

$str = "123.456";
echo preg_replace("/(\.\d\d(?>[1-9]?))\d+/","\\1",$str);

(?>[1-9]?) 就是固化分组,(?>[1-9]?) 成功匹配了 6,因为 (?>[1-9]?) 是固化分组,所以 ? 产生的备选状态被放弃,此时固化分组匹配到的 6 便不能用于正则引擎的回溯。子表达式 \d+ 匹配失败,尝试回溯也失败,导致整个正则表达式第 1 次迭代匹配失败,后面会继续迭代匹配到源字符串的结尾处,最后正则引擎才停止服务,不过后面的几次迭代匹配也都是失败的,那么最终的结果就是,正则表达式没有匹配到任何东西,替换动作也没有执行,这符合我们的需求。

php 还提供了占有优先的语法,效果和固化分组一样:

$str = "123.456";
echo preg_replace("/(\.\d\d[1-9]?+)\d+/","\\1",$str);

在量词 ? 后面加个 +,就变成了占有优先量词,意为着 [1-9]?+ 匹配到 6 后,是没有备选状态的(即无法回溯),这样 \d+ 最后匹配失败,最终导致整个匹配过程失败,没有匹配到任何东西。

虽然 java 不支持固化分组的语法,但 java 也提供了占有优先的语法,同样能够避免正则回溯。如下:

String str = "123.456";
System.out.println(str.replaceAll("(\\.\\d\\d[1-9]?+)\\d+", "$1"));// 123.456

值得注意的是:java 中 replaceAll 方法需要双反斜杠 \\作为转义符号。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值