高性能的正则表达式效率优化

前言

编写高性能的正则表达式,有如下几条规则,这几条规则是本人总结出来的:

1、使用正确的边界匹配器(^、$、\b、\B等)

2、使用具体的元字符、字符类(\d、\w、\s等)

3、使用正确的量词(+、*、?、{n,m})

4、使用非捕获组、原子组

5、注意量词的嵌套

其实正则表达式的很多优化技巧都是围绕着“减少回溯”这样一个原则进行优化的。

至于什么是“回溯”,笔者就不在这里重复了,以下通过具体的例子理解这样的过程。

示例

一、以下是一则匹配电子邮件地址的正则表达式:

^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$

先一步步的解析:

1、^\w+:表示必须以字符开始, 且是一个或者多个;

2、([\.-]?\w+)*中的“[\.-]?”表示匹配“.”或者“-”,零次或者一次;

3、([\.-]?\w+)*中的“\w+”则表示匹配一个或者多个的字符;

4、([\.-]?\w+)*整个则表示匹配.xxx、-xxx或者xxx这样的字符,且零次或者多次;

5、第1-4步,则匹配sunny或者sunny.yang这样的字符;

6、“@”则是具体元,匹配具体的@;

7、 \w+:则表示匹配的一个或者多个的字符,因为email不可能这样嘛:[email protected];

8、 ([\.-]?\w+)*:则跟第2-4步一样,匹配.163、-lib、.gd这样的字符,且零次或者多次;

9、 (\.\w{2,3})+$:则匹配.com、.cc这样结尾的域名,且因为\w{2,3}限定了长度必须为2-3位,所以不能匹配.c、.n这样的字符。

乍看这样一个解析过程没问题,逻辑正确,但其实暗藏很多问题,看看以下的一个匹配图,backtrack则表示回溯(使用RegexBuddy可以很清晰的看到这过程)

整个成功的匹配过程经历了55步,我们先分析下整个匹配过程:

1、图中的第1和2步,匹配^\w+,匹配成功,匹配了“admin”;

2、图中第3步,匹配[\.-]?,当然由于不存在“.”和“-”,因此没匹配上具体的字符,但又由于“?”的限定,可以匹配零或者一次,因此这个子表达式匹配成功,虽然没匹配上具体的字符。

3、图中第4步,匹配\w+,由于“+”限定一个或者多个以上字符,但后续已经没[a-zA-Z0-9]可以匹配了,因此产生回溯,回溯到上次匹配成功的位置,也就admin;

4、图中第5步,因为上一步产生了回溯,所以“[\.-]?\w+”匹配了零次,由于([\.-]?\w+)*中限定零次或者多次,因此也匹配成功,也没匹配上具体的字符;

以下步骤,匹配该过程:

^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$

5、图中第6步,匹配了“@” ,第7步匹配了“\w+”,即匹配了“open”;

6、图中第8-13步,匹配“([\.-]?\w+)*” ,匹配了“-lib”、“.com”,匹配“.com”可能与我们期望不相符,我们期望这子表达式匹配的是www.xx.gd.cn中的“.gd”;

7、图中第7-10步,匹配了open-lib,第7-13步则匹配了open-lib.com;

8、因为“([\.-]?\w+)*”中的量词是“*”,则继续重复这个过程;

9、 图中第14步,匹配“([\.-]?\w+)*”中的“[\.-]?”,因为此时指针已经位于@open-lib.com之后了,但由于量词“?”,因此也匹配成功,但没匹配上字符,也没字符可匹配;

10、 图中第15步,匹配“([\.-]?\w+)*”中的“\w+”,此时指针仍位于字符串末尾,没任何字符能匹配,所以匹配失败,产生回溯,回到上次成功的位置,即图中的第13步,继续下个表达式的匹配;

11、 图中第16步,匹配(\.\w{2,3})+中的“\.”,由于没有任何字符能匹配,匹配失败,进行回溯;

12、图中第17步 ,(\.\w{2,3})+中量词“+”,表示该表达式必须匹配一次或者多次,由于上一步匹配失败了,所以匹配零次,但不符合一次或者多次的限定,因此继续回溯;

13、由于上一步匹配失败,需要进行回溯,因此表达式没有更多的分支了,只能将指针回退一个字符,回溯上次成功的位置,即([\.-]?\w+)*中\w+的位置(这是上次产生分支的位置);

14、图中剩下的步骤,就重复着匹配([\.-]?\w+)*,回退字符,匹配(\.\w{2,3})+这样的过程,直到匹配成功。

二、以下看看另外一则同样匹配邮件地址的正则表达式:

^\w+([-\.]\w+)*@\w+([-\.]\w+)*\.\w+([-\.]\w+)*$

这个正则跟上面的看起来貌似差不多,不过细看还是有区别的,也先一步步来解析:

1、^\w+:表示必须以字符开始, 且是一个或者多个(这一步与上面的一样);

2、 ([-\.]\w+)*中的“[-\.]”表示匹配“-”或者“.”;

3、 ([-\.]\w+)*中的“\w+”则表示匹配一个或者多个的字符;

4、([-\.]\w+)*整个则表示匹配.xxx、-xxx这样的字符,且零次或者多次;

5、第1-4步,则匹配sunny或者sunny.yang这样的字符;

6、“@”则是具体元,匹配具体的“@”;

7、 \w+:则表示匹配的一个或者多个的字符,因为email不可能这样嘛:[email protected];

8、([-\.]\w+)*:则跟第2-4步一样,匹配.163、-lib、.gd这样的字符,且零次或者多次;

9、“\.”则是具体元,匹配“.”;

10、\w+:则匹配一个或者多个字符;

11、([-\.]\w+)*:则匹配“.com”、“-lib”、“.c”这样的字符,且可以零次或者多次;

12、$ :则表示结尾

乍看这个正则的步骤过程貌似比上一则长,其实不然,同时这个正则也存在着问题,先看看匹配图,同样backtrack表示回溯:

对的,你没看错,整个正确的匹配过程用了19步,对比前面的55步,简直天与地的差别。,我们继续分析下匹配过程:

1、图中的第1和2步,匹配^\w+,匹配成功,匹配了“admin”;

2、图中第3步,匹配[-\.],当然由于不存在“.”和“-”,因此没匹配上具体的字符,也没具体的量词允许匹配零次,所以不用继续往下匹配了,因此直接产生了回溯;

3、图中第4步,因为上一步产生了回溯,所以“[-\.]\w+”匹配了零次,由于([-\.]\w+)*中限定零次或者多次,因此也匹配成功,也没匹配上具体的字符;

以下步骤,匹配该过程:

^\w+([-\.]\w+)*@\w+([-\.]\w+)*\.\w+([-\.]\w+)*$

4、图中第6步\w+,匹配了open;

5、图中第7-12步匹配([-\.]\w+)*,匹配了“-lib”和“.com”;

6、因为“([-\.]\w+)*”中的量词是“*”,则继续重复这个过程;

7、图第13步,匹配([-\.]\w+)* ,因为此时指针已经位于@open-lib.com之后了,也没具体的量词允许匹配零次,因此匹配失败,回溯到上次成功的位置;

8、图第14步,匹配 ([-\.]\w+)*$中的“[-\.]”,此时指针仍位于字符串末尾,没任何字符能匹配,所以匹配失败,产生回溯,回到上次成功且还没尝试过的位置,即图中的第9步;

9、经过上面的回溯,指针已经位于@open-lib之后的位置了;

10、图第15步匹配了“.”,第16步\w+则匹配了“com” ;

11、图第17步匹配([-\.]\w+)*,由于此时指针又位于字符串末尾,因此[-\.]部分没匹配上任何字符,因此产生回溯;

12、图第18步,由于 ([-\.]\w+)*的量词是“*”,表示匹配零次或多次,虽然子表达式[-\.]匹配失败,所以整个表达式匹配了零次,也是匹配成功;

13、最后一步第19步,“$”表示末尾匹配,因为此时指针位于字符串末尾,故符合,因此也匹配成功。

分析

整个匹配过程关键优化地方,还是回溯,两个示例表达式看起来相近,匹配过程也部分类似,但两个例子的效率却如此大的分别,现在来分析一下造成回溯的原因。

对比下两个表达式不同的部分:

^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$

^\w+([\.-]\w+)*@\w+([\.-]\w+)*\.\w+([-\.]\w+)*$

时间: 2024-07-30 18:05:15

高性能的正则表达式效率优化的相关文章

php程序效率优化的一些策略小结

php程序效率优化的一些策略小结 1.在可以用file_get_contents替代file.fopen.feof.fgets等系列方法的情况下,尽量用 file_get_contents,因为他的效率高得多!但是要注意file_get_contents在打开一个URL文件时候的PHP版本问题; 2.尽量的少进行文件操作,虽然PHP的文件操作效率也不低的; 3.优化Select SQL语句,在可能的情况下尽量少的进行Insert.Update操作(在update上,我被恶批过); 4.尽可能的使

漫游Kafka设计篇之效率优化

原文地址:http://blog.csdn.net/honglei915/article/details/37564757 Kafka在提高效率方面做了很大努力.Kafka的一个主要使用场景是处理网站活动日志,吞吐量是非常大的,每个页面都会产生好多次写操作.读方面,假设每个消息只被消费一次,读的量的也是很大的,Kafka也尽量使读的操作更轻量化. 我们之前讨论了磁盘的性能问题,线性读写的情况下影响磁盘性能问题大约有两个方面:太多的琐碎的I/O操作和太多的字节拷贝.I/O问题发生在客户端和服务端之

高性能网站建设和优化

高性能网站建设和优化 在前端中,性能优化就是优化用户体验,减少响应速度以助于提高网页整体的加载速度,提高性能在建设高性能页面中不可缺少的一部分. 改进性能分为从前端改进和后台改进. 可以说前端是改进编辑页面的一些细节,在建设页面编辑代码是就注意性能的优化,然后集少成多 ,响应和加载时间通过逐步的减少,到达性能的改进, 而且在前端优化中需要的时间和资源都较少. 而要从后台入手改进性能,就相对来说复杂,也会带来很多的改动. 前端 作为前端开发人员,就要把性能优化的重点放在前端优化中.在代码编辑中考虑

c++程序的效率优化初涉

能写出稳定高效的程序一直是程序员所追求的,今天就和大家一起探讨一下关于C++程序优化的几点看法. 由于C/C++语言的复杂性,致使C++编译器隐藏了层层幔布,我们不经意的一条语句都可能是编译器幕后几经周折的结果,在要求程序高效运行的环境下,每一条语句都会让我们慎之又慎,而程序优化又是个十分广泛的话题,包括程序架构设计的优化,语言本身的优化,编程技巧和策略等等,如此大的范围非我能力所及,这里谈的优化就是在实际开发中遇到的问题. 一.  举手之劳的小差别 既然说优化就一定要仔细,不放过任何微小的细节

(转)as3效率优化

1.改进算法无论对于那一种程序,好的算法总是非常重要的,而且能够极大地提高程序性能,所以任何性能的优化第一步就是从算法或者说程序逻辑的优化开始,检查自己的程序是否有多余的运算,是否在没有必要的时候做了无用功,往往从这些方面就能找到那些导致性能低下的地方. 2.优化细节代码针对细节总是好的,有一些小技巧比如:用 var obj:Object = {}; 要比 var obj:Object = new Object();要好:var arr:Array = []; 要比 var arr:Array

对Listview控件的效率优化

不管在Android平台还是IOS平台,Listview或者是类似控件,在数据显示方面都占据着相当重要的位置.而作为最重要的数据展示形式,Listview控件或者是类似的需要使用Adapter的控件的加载以及数据展示的效率和优化,就被摆在了一个很重要的位置,本篇文章主要给大家介绍,如何可以实现Listview控件的效率优化. 1.重用已经生成过的Item View 我们都知道,Listview的数据显示,少不了Adapter的设计,所以优化在重点都在如何设计Adapter中.而BaseAdapt

游戏效率优化(2) 使用const关键字

游戏效率优化(2)使用const关键字 DionysosLai  2014-5-15 使用const有很多好处,比方保护被修饰的东西,防止意外修改,提高程序健壮性等作用.不过使用const 可以提高程序运行效率,却很少有人知道. 下面看几个例子,在看cocos2d源码时,我们经常会看到如下类似的代码: ccpAdd(const CCPoint& v1, const CCPoint& v2) { return v1 + v2; } ccpSub(const CCPoint& v1,

jquery选择器效率优化问题

jquery选择器效率优化问题   jquery选择器固然强大,但是使用不当回导致效率问题: 1.要养成将jQuery对象缓存进变量的习惯 //不好的写法 $('#btn').bind("click",function() {}); $('#btn').css("border","1px solid red"); $("#btn").css("background-color","green&qu

(同事的原创)关于效率优化的一点工作心得

文是单位同事胡计平的一个关于效率优化的总结,内容很实用,转贴到blog里,以备自己日后查看,也希望能对更多的人有所帮助 最近写一程序,跟效率优化打上了交道,把其中的体会写下来,供大家讨论分享,我想效率优化工作可以分为如下几个步骤: (1)查找影响效率的瓶颈之处:定位的方法当然是使用时间函数,一般精确的使用GetTickCount就可以,非常精确的使用 function GetCycleCount: Int64;asm  RDTSC;    //得到当前CPU的时钟周期数.end; 想必这个知识大