为什么这三个正则表达式具有不同的步数?

时间:2016-10-08 05:46:56

标签: regex pcre

answer to a recent question中,我设计了几个聪明的小正则表达式(在提问者的请求下)来匹配字符串开头或结尾的子字符串。然而,当在Regex101上运行时,我注意到不同的模式具有不同的步数(表明正则表达式引擎必须为一个与另一个做更多的工作)。但是,在我看来,没有直观的理由认为应该如此。

这三种模式如下:

  • 有条件的乐趣:/(^)?!next(?(1)|$)/demo - 86步)
  • 经典轮换:^!next|!next$demo - 58步)
  • 讨厌的外观:!next(?:(?<=^.{5})|(?=$))demo - 35步)

为什么第一种模式的效率低于第二种模式,而且最令人困惑的是,为什么第三种模式如此有效?

1 个答案:

答案 0 :(得分:5)

TL; DR

  

为什么第一个模式的效率低于第二个模式,并且,   最令人困惑的是,为什么第三个如此高效

因为前两个是锚定的,第三个不是。

真实故事,如何采取措施

考虑到这个正则表达式/^x/gm,您认为引擎将采取多少步骤来返回&#34;不匹配&#34;如果主题字符串是abc?你是对的,两个。

  1. 断言字符串的开头
  2. 匹配x
  3. 然后整体匹配失败,因为在字符串断言开始后没有x立即出现。

    我说谎了。这不是我讨厌,它只是让你更容易理解即将发生的事情。根据{{​​3}},它根本不采取任何步骤:

    regex101.com

    这次你相信吗?是。不,我们来看看。

    PCRE启动优化

    PCRE,善待其用户,提供一些功能来加速称为启动优化的事情。根据正在使用的正则表达式,它会进行一些依赖的优化。

    这些优化的一个重要特征是主题字符串的预扫描,以确保:

    • 主题字符串包含至少一个与匹配的第一个字符或
    • 对应的字符
    • 如果存在已知的起点。

    如果找不到匹配函数,则永远不会运行。

    如果我们的正则表达式为/x/且主题字符串为abc,那么启用启动优化时,就会进行预扫描以查找x ,如果没有发现整场比赛失败或更好,它甚至不会费心去完成匹配过程。

    那么这些信息如何帮助

    让我们回顾一下我们的第一个例子并稍微改变我们的正则表达式。从:

    /^x/gm
    

    /^x/g
    

    差异是m标志未被设置。对于那些不知道m标志如何设置的人:

    它改变了^$符号的含义,因为它们不再是字符串的开头和结尾,而是行的开头和结尾。

    现在,如果我们在主题字符串/^x/g上运行此正则表达式abc,该怎么办?我们是否应该预期步进引擎的差异与否? 绝对,是的。让我们看一下enter image description here返回的信息:

    regex101.com

    我真的鼓励你这次相信它。这是真实的。

    发生了什么?

    嗯,这看起来有点令人困惑,但我们会启发。如果没有设置m修饰符,则预扫描会查找断言字符串的开头(已知起点)。如果断言通过则实际匹配函数运行,否则&#34;不匹配&#34;会回来的。

    但等等......每个主题字符串肯定都有一个且只有字符串位置的开头,并且它总是在它的开头。那么扫描显然不是必要的吗?是的,引擎在这里没有进行预扫描。使用/^x/g它会立即断言字符串的开始,然后像这样失败(因为它匹配^它会经历实际的匹配过程)。这就是我们看到enter image description here显示步数 2 的原因。

    但是......设置m修饰符会有所不同。现在改变了^$锚点的含义。在^匹配起始行的情况下,主题字符串abc中的相同位置的断言发生但下一个直接字符不是x,在实际匹配过程中并且从g标志开始已开启,下一场比赛从b之前的位置开始并失败,此试错将继续到主题字符串结束。

    regex101.com

    调试器显示 6 步骤,但主页面显示 0 步骤,为什么?

    我不确定后者但是为了调试,regex101调试器与(*NO_START_OPT)一起运行,所以只有设置了这个动词才有6个步骤。我说我不确定后者,因为 所有锚定模式阻止了进一步的扫描前优化 ,我们应该知道什么可以称为 { {3}}

      

    如果所有的模式都由PCRE自动锚定   顶级替代品以下列之一开头:

         
        
    • ^除非设置PCRE_MULTILINE
    •   
    • \A总是
    •   
    • \G总是
    •   
    • .*如果设置了PCRE_DOTALL并且没有后向引用   出现.*的子模式
    •   

    现在,当我在m/^x/g标志 设置为m时没有预先扫描时,您完全得到了我所说的内容:它是被认为是一种禁用预扫描优化的锚定模式。因此,当/^x/gm标志打开时,这不再是锚定模式:\A因此可以进行预扫描优化。

    enter image description here

    引擎知道字符串锚^的开始(或多行模式禁用时^)只在匹配时发生一次,因此它不会在下一个位置继续。

    返回您自己的RegExes

    前两个被锚定(m(*NO_START_OPT)!next(?:(?<=^.{5})|(?=$)) 标志一起),第三个不是。也就是说,第三个正则表达式受益于预扫描优化。您可以相信 35 步骤,因为优化导致了这一步骤。但是,如果禁用启动优化:

    {{1}}

    您将看到57个步骤,这些步骤与调试器步骤的数量大致相同。