正则表达式的编写思路
一个避免过多匹配的小技巧
前面我们已经多此谈到书写不合理的正则表达式引起过多匹配的问题,现在的问题是,如何可以尽量避免类似的情况发生。这里有个小小的技巧。
如果你发现你定制模式匹配了过多的结果,一个好的方法是换个思路,与其考虑我的模式下一步需要匹配什么,不如考虑我的模式下一步需要避免匹配什么。我们可以用元字答“^”和字符类很容易的达成这种效果,这常常可以得到更精确的匹配。
为了说明这种思路的好处我们先来举一个与正则表达式无关的例子,考虑这样一个问题,你把一个骰子一次抛出6的概率是六分之一,如果让你掷六次,掷出一个6的概率是多少呢?
可能有人会这么算,一次的概率是1/6,六次是就是6个1/6,加起来等于1。这个结果明显是错的,虽然你掷了六次,但肯定不能保证必然会掷出一个6。从正向的思路解这道题看上去有点难。
如果我们换个思路,解决的方法就明确多了。我们可以把这个题的问法改成这样,如果让你掷6次骰子,每一次都掷不出6的概率是多少?这个问题就好解多了,根据概率的乘法原理,每一次掷出不是6的点数的概率是5/6,而6次中每一次都不是6的概率是5/6的6次方,大概等于33%的样子,然后用1减去这个数字就可以得到我们需要的答案。
你可以把模式中每部分的匹配看作掷一次骰子的过程,每一部分的匹配概率与总匹配概率的情况与我们上面这个例子非常相似。
如何提高正则表达式的解析效率
对同样匹配内容的正则表达式而言,一些模式往往比另外一些模式更有效率。举一个简单的例子,使用字符类“[aeiou]”会比使用分支选择型模式“(a|e|i|o|u).”更有效,一般而言,使用尽可能简单和基本的模式通过会得到更高的效率。
应该尽可能的慎用相互嵌套的无限重复量词,当遇到不匹配的目标字符串时,对字符串的解析有可能花掉很可观的时间。比如下面这个模式片断“(a+)*”,当遇到不匹配的目标字符串“aaaa”时,解析器会对它尝试33种不同的匹配方法,这个数目会随不匹配字符串长度的增加而极快的增长。
一些正则表达式工具对一些特定的模式匹配进行了优化以提高效率,了解你使用的正则表达式工作做过些什么优化并尽可能利用经过优化的模式可以大大提高你的正则表达式执行效率。例如,PHP对形如/a+)*b /这样的模式的解析进行了优化,当模式结尾是一个确定的字符时,解析器会先查找目标的结尾是否符合模式,如果否则立刻返回失败的匹配结果并停止解析。如果将上面的样式改为“(a+)*\d”时,因为结尾不再是一个确定的字符,此模式会按正常的过程解析。如果你想看一下两者效果的差异,你在我们前面提到的工具中,把目标字符串设置成25个小写的a字符,然后分别测试两个模式,前者立刻就结束了,而后者需要等待约一秒(笔者使用的是XP1700+处理器)。
除了尽可能利用经过优化的模式,对一些模式进行重新构造也可以大大提高效率。我们在介绍后向断言时介绍过的那个利用后向断言结合一次性子模式匹配结尾的字符的方法就是一个很好的例子。
这里我们准备结束这个教程,由于篇幅和本人水平的限制文中可能会有很多疏漏,还要请求大家谅解。对正则表达式介绍最全面的可能还是Perl相关的一些文档和著作,如果想对正则表达式进行更深入的了解可以参看Jeffrey Friedl 写的“Mastering Regular Expressions”一书,里面有很多例子。不过我觉得在了解正则表达式基本概念后,还是仔细读一下自己经常使用的相关工具里的正则表达式相关部分更实用一些,最后,还是那句话,实践出真知,希望大家在不断实践中更好的掌握正则表达式的使用。