Java性能调优:优化正则表达式的匹配效率

file

在我们的日常业务开发中经常会涉及到使用正则表达式对数据进行处理,比如String的Split()方法,它根据方法中传入的正则表达式对字符串做分割处理。

但是我们是否真的了解正则表达式,它是如何匹配的?不同的匹配方式会带来怎样的效率差别?怎样才能做到效率最优?

本篇就对“如何优化正则表达式的匹配效率?”做深入探讨。

 

一、匹配的三种方式

看下面这个例子,我们给定了一个字符串以及三个功能相同但写法略有区别的正则表达式:

String testStr = "effg";
String regular_1 = "ef{1,3}g";
String regular_2 = "ef{1,3}?g";
String regular_3 = "ef{1,3}+g";

split方法测试每个正则表达式运行的时间:

List<String> regulars = new ArrayList<>();
regulars.add(regular_1);
regulars.add(regular_2);
regulars.add(regular_3);

for(String regular : regulars){
    long start,end;
    start = System.currentTimeMillis();
    testStr.split(regular);
    end = System.currentTimeMillis();
    System.out.println((end - start) + "(ms)");
}

控制台输出(为了体现效率差别,测试的时候我将上面的字符串复制成了足够的长度):

2(ms)
1(ms)
0(ms)

可以明显看到,虽然实现了相同的匹配功能,但效率却有所区别,原因在于这三种写法定义了正则表达式的三种匹配逻辑,我们来逐一说明:

码文不易
你的关注是浩说编程持续更新的动力
浩说编程会努力做的更好

 

1、贪婪模式(Greedy): ef{1,3}g

贪婪模式是正则表达式的默认匹配方式,在该模式下,对于涉及数量的表达式,正则表达式会尽量匹配更多的内容,我用模型图来演示一下匹配逻辑

图片

到第二步的时候其实已经满足第二个条件f{1,3},但我们说过贪婪模式会尽量匹配更多的内容,所以依然停在第二个条件继续遍历字符串

图片

注意看第四步,字符g不满足匹配条件f{1,3},这个时候会触发回溯机制:指针重新回到第三个字符f处

图片

关于回溯机制

回溯是造成正则表达式效率问题的根本原因,每次匹配失败,都需要将之前比对过的数据复位且指针调回到数据的上一位置,想要优化正则表达式的匹配效率,减少回溯是关键。

回溯之后,继续从下一个条件以及下一个字符继续匹配,直到结束

图片

2、懒惰模式(Reluctant): ef{1,3}?g

与贪婪模式相反,懒惰模式则会尽量匹配更少的内容:

图片

到第二步的时候,懒惰模式会认为已经满足条件f{1,3},所以会直接判断下一条件

图片

注意,到这步因为不满足匹配条件,所以触发回溯机制,将判断条件回调到上一个

图片

回溯之后,继续从下一个条件以及下一个字符继续匹配,直到结束

图片

3、独占模式(Possessive): ef{1,3}+g

独占模式应该算是贪婪模式的一种变种,它同样会尽量匹配更多的内容,区别在于在匹配失败的情况下不会触发回溯机制,而是继续向后判断,所以该模式效率最佳

图片

在了解了三种匹配方式的匹配逻辑之后,给出第一个优化建议

 

优化建议一:

推荐在使用正则表达式的时候,采用独占模式效率最佳,因为触发回溯的概率最小。

 

二、优化正则中的分支选择

通过上面对正则表达式匹配逻辑的了解,我们不难想到,由于回溯机制的存在,带有分支选择的正则表达式必然会降低匹配效率

String testStr = "abbdfg";
String regular = "(aab|aba|abb)dfg";

在这个例子中,“aab”并未匹配,于是回溯到字符串的第一个元素重新匹配第二个分支”aba”,以此类推,直到判断完所有分支,效率问题可想而知。

那么应该如何优化呢?这里给出特定情况下的两种优化建议:

优化建议二:

首先,如果分支中存在公共前缀可以提取公共部分

String regular = "a(ab|ba|bb)dfg";

这样首先减少了公共前缀的判断次数,其次降低了分支造成的回溯频率,相比之下效率有所提升。

优化建议三:

第二种方式是,如果分支中的元素比较简单,可以使用indexOf方法匹配

testStr.indexOf("aab");
testStr.indexOf("aba");
testStr.indexOf("abb");

虽然这两个建议能够优化匹配效率,但只是相比之下的优化,分支选择的机制就决定了势必要进行多次的回溯判断,所以分支选择建议能不用就不用。

 

三、优化正则中的捕获组

捕获组在正则表达式中通常用”()”表示,它将其中匹配到的内容保存到一个数组中,以便之后使用。

例如我们想获取前端input中的内容:

String inputStr = "<input id=\"userName\">userName</input>";

定义带有捕获组的正则表达式,并输出捕获组存入数组中的内容

String regular = "(<input.*?>)(.*?)(</input>)";
Pattern pattern = Pattern.compile(regular);
Matcher matcher = pattern.matcher(inputStr);
while(matcher.find()){
  System.out.println(matcher.group(0));
  System.out.println(matcher.group(1));
  System.out.println(matcher.group(2));
  System.out.println(matcher.group(3));
}

控制台输出:

<input id=\"userName\">userName</input>
<input id=\"userName\">
userName
</input>

看到这里大家应该理解了捕获组的用法,第三行就是我们需要的内容,但需要优化的是,第二行以及第四行的内容我们并不需要,这会影响效率以及内存损耗。

对于这种情况我们可以使用”(? : )“来代替”()”,区别在于前者不会将匹配的内容存入数组:


String regular = "(?:<input.*?>)(.*?)(?:</input>)";
Pattern pattern = Pattern.compile(regular);
Matcher matcher = pattern.matcher(inputStr);
while(matcher.find()){
  System.out.println(matcher.group(0));
  System.out.println(matcher.group(1));
}

控制台输出:

<input id=\"userName\">userName</input>
userName

优化建议四:

对于存在捕获组的正则表达式,如果信息不需要保存,则使用”(?: )“来替代”()”

 

总结

本篇针对正则表达式的三个点:匹配模式、选择分支、捕获组,分析出了三个优化建议:

1、推荐在使用正则表达式的时候,采用懒惰模式和独占模式效率最佳,因为触发回溯的概率最小。

2、分支选择建议尽量避免使用,特定条件下可以采用提取公共前缀、indexOf方法优化

3、对于存在捕获组的正则表达式,如果信息不需要保存,则使用”(?:“来替代”()”

以上就是本篇的内容,希望对读者有所帮助,浩说编程帮你学到更多。

发布者:小站,转转请注明出处:http://blog.gzcity.top/4658.html

(1)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022年7月5日 10:59
下一篇 2022年7月6日 10:58

相关推荐

  • 使用FFmpeg实现抠图合并功能(chroma key)

    自己以前实现抠图算法好久了,没想到ffmpeg里面早有这个功能了在很多视频中可以看到图像是合成的,例如有些神剧里面某大侠跳下万丈深渊的场景,某人在三昧真火中被烧的场景,还有些游戏主播,体育主播在效果图如下: 在最新版本的ffmpeg中,已经增加了chroma key功能的filter,只需要一条命令即可搞定 ./ffmpeg –i ~/fuck….

    2022年7月11日
    5.9K11970
  • Java多线程方式快速解析大量文本内容得到pdf链接转换为文本-学习笔记

    Java多线程方式快速解析大量文本内容得到pdf链接转换为文本-学习笔记 解析流程: 1、一个目录里面包含大量多种格式文件;2、从目录中提取txt为后缀的文件路径List;3、根据服务器性能设定多线程处理文本数量;4、txt文本中获取所有的链接;5、通过正则提取后缀为.pdf链接;6、下载pdf文件到本地;7、使用开源工具把pdf转换为txt内容;获取链接正…

    2022年5月2日
    8.6K11820
  • 从mysql 到kingbase(人大金仓)

    一、背景介绍 公司主要做toG 的项目,经常服务于各个地方政府。之前某省会城市下属区县项目,需要将已交付的项目中所使用到的各类中间件全部替换为国产,其中就包括了数据库的替换。项目中一直使用的是mysql数据库,后经商务调研及选型,决定替换为国产数据库 kingBase (人大金仓数据库),并由我负责所有应用的代码迁移工作。 我们的项目采用的是springCl…

    2023年7月31日
    7.3K00
  • Intellij IDEA激活码,IDEA稳定专属激活码

    2018.2~2023版本用这个,下面激活码直接复制粘贴到activation code即可激活软件,如果是新的版本,换新激活码的时候code with me也要激活一次 FDXL1Y2811-eyJsaWNlbnNlSWQiOiJGRFhMMVkyODExIiwibGljZW5zZWVOYW1lIjoiQmVub2l0IE1lbmVuZGV6IiwiYXN…

    2023年4月13日
    11.1K1750
  • ChatGPT,什么来头?

    ChatGPT正在掀起一股新的人工智能热潮。 最近大火的ChatGPT,获得微软巨额投资,连比尔·盖茨都盛赞:“AI将成为2023年最热门的话题,那是合适的。这(AI)与 PC 和互联网一样重要……这让我能一窥未来,整个平台的方法和创新速度都令我印象深刻。” 不少商界大佬也纷纷夸赞ChatGPT。 马斯克表示,“ChatGPT很惊人,…

    2023年2月15日
    1.2K60

回复 meleant

您的邮箱地址不会被公开。 必填项已用 * 标注

评论列表(2条)