在回答最近的一个问题时,我设计了几个聪明的小正则表达式(根据提问者的要求)匹配字符串开头或结尾的子字符串.然而,当在Regex101上运行时,我注意到不同的模式具有不同的步数(表明正则表达式引擎必须为一个与另一个做更多的工作).然而,在我看来,没有直观的理由认为这应该如此.
三种模式如下:
/(^)?!next(?(1)|$)/(演示 - 86步)^!next|!next$(演示 - 58步)!next(?:(?<=^.{5})|(?=$))(演示 - 35步)为什么第一种模式的效率远远低于第二种模式,而且最令人困惑的是,为什么第三种模式如此有效?
为什么第一种模式的效率远远低于第二种模式,而且最令人困惑的是,为什么第三种模式如此有效?
因为前两个是锚定的,第三个不是.
考虑到这个正则表达式/^x/gm,如果主题字符串是abc什么,你认为引擎将采取多少步骤来返回"不匹配" ?你是对的,两个.
x然后整体匹配失败,因为x在字符串断言开始后没有立即出现.
好吧,我骗了.这不是我讨厌,它只是让你更容易理解即将发生的事情.根据regex101.com,它完全没有任何步骤:
你这次相信吗?是.不,我们来看看.
PCRE 启动优化PCRE善待用户,提供一些功能来加速所谓的启动优化.它根据使用的正则表达式进行一些依赖的优化.
这些优化的一个重要特征是对主题字符串进行预扫描,以确保:
如果找不到匹配函数,则永远不会运行.
说,如果我们的正则表达式/x/和我们的主题字符串abc然后启用了启动优化,则预先进行扫描以查找x,如果未找到整个匹配失败或更好,它甚至不打扰通过匹配过程.
那么这些信息如何帮助?
让我们回到我们的第一个例子并稍微改变我们的正则表达式.从:
/^x/gm
Run Code Online (Sandbox Code Playgroud)
至
/^x/g
Run Code Online (Sandbox Code Playgroud)
差异是m没有设置的标志.对于那些不知道m如果设置了什么标志的人:
它改变了^和$符号的含义,因为它们不再意味着字符串的开始和结束,而是行的开始和结束.
现在如果我们/^x/g在主题字符串上运行此正则表达式abc怎么办?我们是否应该预期步进引擎的差异与否?当然,是的.让我们来看看regex101.com返回的信息:
我真的鼓励你这次相信它.这是实际的.
发生了什么?
好吧,这看起来有点令人困惑,但我们会启发.如果没有m设置修饰符,则预扫描会查找字符串的开头(已知起点).如果断言通过则实际匹配函数运行,否则将返回"不匹配".
但是等等......每个主题字符串肯定只有一个字符串位置的开始,它总是在它的开头.那么预扫描显然不是必要的吗?是的,引擎不会在这里进行预扫描.随着/^x/g它立即断言字符串的开始,然后像这样失败(因为它匹配^它通过实际匹配过程).这就是我们看到regex101.com显示步数为2的原因.
但是......设置m修饰符的东西各不相同.现在两者^和$锚的意义都改变了.使用^匹配的行开头,主题字符串中相同位置的断言abc发生但下一个立即字符不在x,在实际匹配过程中并且由于g标志打开,下一个匹配从之前的位置开始b并且失败并且此试验和错误继续结束主题字符串.
调试器显示6个步骤,但主页面显示0个步骤,为什么?
我不确定后者,但为了调试,regex101调试器运行,(*NO_START_OPT)因此只有设置了这个动词才有6个步骤.我说我不确定后者,因为所有锚定模式都阻止了进一步的预扫描优化,我们应该知道什么可以称为锚定模式:
如果所有顶级备选方案都以下列之一开头,则模式将由PCRE自动锚定:
^除非PCRE_MULTILINE设定\A总是\G总是.*如果PCRE_DOTALL已设置且没有对.*出现的子模式的后引用
现在,当我说没有预先扫描时没有设置m标志时,你完全得到了我所说的内容:它被认为是一种禁用预扫描优化的锚定模式.因此,当标志打开时,这不再是锚定模式:因此可以进行预扫描优化./^x/gm/^x/gm
引擎知道字符串锚点的开始\A(或多线^模式禁用时)仅在匹配时发生一次,因此它不会在下一个位置继续.
前两个是锚定的(^与m标志一起),第三个不是.也就是说,第三个正则表达式受益于预扫描优化.您可以相信35个步骤,因为优化导致了它.但是如果禁用启动优化:
(*NO_START_OPT)!next(?:(?<=^.{5})|(?=$))
Run Code Online (Sandbox Code Playgroud)
您将看到57个步骤,这些步骤与调试器步骤的数量大致相同.