这个正则表达式是低效的。为什么它会崩溃Sublime Text 2的堆栈?

时间:2013-04-02 15:47:00

标签: regex lookahead

已经回答了:

这个正则表达式有效:

<item>(?:(?!</item>).|\n)*?(?:(?=201[0-3]</pubDate>))(?:(?!</item>).|\n)*?</item>

虽然这个崩溃了堆栈:

<item>(?:(?!</item>).|\n)*(?:(?=201[0-3]</pubDate>))(?:(?!</item>).|\n)*</item>

这也有效,没有前瞻:

(?s)<item>.*?201[0-3]</pubDate>.*?</item>

原始问题:

我在Sublime Text 2中有一个XML文件(下面的例子)。我想找到所有<item&gt;包含<pubDate&gt;的元素2010年至2013年的元素。

上面的正则表达式工作正常,但是当我找到所有(文件大约1MB,大约120个匹配)时,ST2用完了堆栈空间。

潜伏在哪些可怕的低效率?

示例XML:

<?xml version="1.0" encoding="utf-8"?>
    <channel>
        <item>
            <title>This will match</title>
            <link>http://gcanyon.posterous.com/</link>
            <pubDate>Sat Mar 10 10:22:00 -0800 2012</pubDate>
            <dc:creator><![CDATA[Geoff Canyon]]></dc:creator>
        </item>
        <item>
            <title>This won't</title>
            <link>http://gcanyon.posterous.com/</link>
            <pubDate>Tue Jun 30 05:01:32 -0700 2009</pubDate>
            <dc:creator><![CDATA[Geoff Canyon]]></dc:creator>
        </item>
    </channel>
</rss>

2 个答案:

答案 0 :(得分:2)

贪婪的贪婪正则表达式。例如:

(?:(?!</item>).|\n)*

会一直到下一个</item>,而它不是你想要的,你只是不希望它走得更远,我会假设。

你应该在lazy operators找到快乐。

PS:抱歉,我没有足够的时间深入了解你的正则表达式。希望它能解决你的问题。

答案 1 :(得分:2)

我认为你有两个问题。一个是你的整个方法(如果你只是想要我的真实建议,跳到底部),但看起来另一个是catastrophic backtracking

为什么会破坏

如果我们稍微简化你的模式,可以归结为:

{a}{x*}{x*}{b}

注意两个x*彼此相邻?是的,它们之间有一个(?=y),但让我们忽略它一分钟,因为我不认为引擎正在有效地使用它来限制它正在做的工作量。假设您有一个类似axxxxxxxb的字符串,并且您希望将其与模式匹配。由于有两个x*令牌彼此相邻,因此引擎无法轻易判断一个组的结束位置和另一个组的开始位置。所以它试图将它们全部放在第一个{x*}桶中,因为*是贪婪的:

{a}{xxxxxxx}{}{b}

很好,对吗?它匹配,所以我们可以继续前进。但请考虑axxxxxxQxb之类的内容。这在第一遍时不匹配,因此引擎必须不断尝试排列:

{a}{xxxxxxx}{}{Q} #nope
{a}{xxxxxx}{x}{Q} #nope
{a}{xxxxx}{xx}{Q} #nope
...

最终,这需要很长时间才会炸毁你的筹码。

改进正则表达式

那么如何解决呢?嗯,就是这样:

(?:(?=201[0-3]</pubDate>))

我认为如果引擎是肯定的代币,而不是前瞻,引擎会做得更好。无论如何,它不需要是一个先行;你可以使用它(有或没有\s*):

201[0-3]\s*</pubDate>

之后的(?:(?!</item>).)*是多余的;你应该只需要一个懒惰的.*?

此外,您可以使用多行选项使.也匹配换行符,但我不确定这是否会在速度/执行方面产生任何影响。但是,写作会更短。

整个事情看起来像是:

<item>(?:(?!</item>).)*?201[0-3]</pubDate>.*?</item>  #plus the /m flag

REAL解决方案

但我认为真正的问题是你using regex at all。这看起来像XML。你为什么不使用XML解析器?如果您使用的是.NET,LINQ to XML非常适合您所描述的确切工作,包括有关嵌套pubdate中特定值的部分。应该比正则表达式更简单,更有效。