正则表达式匹配集合对象在Regex.Matches之后出现\"功能评估超时"问题

6

我对C#和正则表达式都比较新,但我已经花了几个小时来寻找解决这个问题的方法,希望对你们来说这很容易 :)

我的应用程序使用正则表达式在给定字符串中匹配电子邮件地址,然后循环遍历匹配项:

String EmailPattern = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
MatchCollection mcemail = Regex.Matches(rawHTML, EmailPattern);

foreach (Match memail in mcemail)

工作正常,但是当我从某个页面下载字符串http://www.sp.se/sv/index/services/quality/sidor/default.aspx时,MatchCollection(mcemail)对象在循环中“挂起”。当使用断点并访问对象时,我会在所有内容(.Count等)上得到“函数评估超时”。

更新 我已经尝试了我的模式和其他电子邮件模式在同一个字符串上,每个人(regex desingers、基于Python的网页等)都失败/超时,当试图匹配这个特定的字符串。

如何检测匹配集合对象是否“准备好”使用?


5
这是一个提示:如果在字符串前加上@符号,你就不需要使用双反斜杠了。字符串内容如下:@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" - R. Martinho Fernandes
3个回答

2
如果您能发布导致问题的电子邮件(以某种方式匿名化),那将为我们提供更多信息,但我认为问题就在这里这个小家伙:
([-.]\\w+)*\\.\\w+([-.]\\w+)*

为了理解问题,让我们将其分成几组:
([-.]\\w+)*
\\.\\w+
([-.]\\w+)*

匹配\\.\\w+的字符串是匹配[-.]\\w+的一个子集。所以,如果您的输入的一部分看起来像foo.bar.baz.blah.yadda.com,则您的正则表达式引擎无法知道哪个组应该匹配它。这有道理吗?因此,第一个([-.]\\w+)* 可以匹配.bar.baz.blah,然后\\.\\w+可以匹配.yadda,最后一个([-.]\\w+)*可以匹配.com...

...或者第一个条款可以匹配.bar.baz,第二个可以匹配.blah,最后一个可以匹配.yadda.com。由于引擎不知道哪个正确,它将继续尝试不同的组合。它应该最终停止,但这可能仍需要很长时间。这被称为“灾难性回溯”。

这个问题还因为您使用了捕获组而不是非捕获组而更加严重;即([-+.]\\w+)而不是(?:[-+.]\\w+)。这会导致引擎尝试分离和保存括号内的任何匹配项以备以后参考。但正如上面所解释的那样,每个子字符串属于哪个组是模糊的。

您可以考虑用类似以下内容替换@后面的所有内容:

\\w[-\\w]*\\.[-.\\w]+

这需要进行一些完善以使其更加具体,但总体的概念已经清楚了。希望我解释得足够好;分组和回溯有点难描述。

编辑:

回顾您的模式,这里还存在一个更深层次的问题,仍然与我提到的回溯/歧义问题相关。子句 \\w+([-.]\\w+)* 本身就是有歧义的。将其分成几个部分,我们有:

\\w+
([-.]\\w+)*

假设您有一个字符串,如foobar\\w+ 的结束位置在哪里,([-.]\\w+)* 开始呢?([-.]\\w+) 重复多少次?以下任何一种都可以作为匹配项:
f(oobar)
foo(bar)
f(o)(oba)(r)
f(o)(o)(b)(a)(r)
foobar
etc...

正则表达式引擎不知道哪个更重要,因此它会尝试所有可能性。这是我上面指出的同样的问题,但它意味着您在模式中有多个位置存在该问题。
更糟糕的是,由于\\w后面跟着+([-.]\\w+)*也是含糊不清的。在blah中有多少组?我数了16种可能的组合:(blah)(b)(lah)(bl)(ah)...
即使对于相对较小的输入,可能的不同组合数量也会非常庞大,因此您的引擎将超负荷运行。如果我是您,我肯定会简化它。

谢谢,我会尝试更改。关于哪个电子邮件引起了问题,我看不到。MatchCollection对象的成员不是“可访问的”。我尝试了不同的模式(请参见我的帖子中的更新),但似乎都卡住了...“灾难性回溯”:D - Johan Helsén
@Johan Helsén - 查看该页面的源代码,我看到了六个电子邮件地址。我怀疑如果您尝试在任何包含六个电子邮件地址的页面上运行该程序,由于正则表达式的资源强度,程序将会挂起。顺便说一下,看看我的编辑。 - Justin Morgan
感谢您的解释。正如您通过查看我的正则表达式所注意到的那样,我还有很多要学习的。 - Johan Helsén

1

我刚刚进行了本地测试,似乎纯粹的文档大小或者 ViewState 中的某些内容导致正则表达式匹配评估超时。(编辑:我非常确定是大小问题。实际上,移除 ViewState 只会显著减小文件大小。)

解决这个问题的一种相对简单的方法可能如下:

string[] rawHtmlLines = File.ReadAllLines(@"C:\default.aspx");
string filteredHtml = String.Join(Environment.NewLine, 
    rawHtmlLines.Where(line => !line.Contains("_VIEWSTATE")).ToArray());
string emailPattern = @"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";
var emailMatches = Regex.Matches(filteredHtml, emailPattern);

foreach (Match match in emailMatches)
{
    //...
}

总的来说,我怀疑电子邮件模式只是没有被很好地优化(或者意图)以过滤大字符串中的电子邮件,而只是用作用户输入验证。通常最好将您搜索的字符串限制为实际感兴趣的部分,并尽可能保持其尽可能小 - 例如通过省略保证不包含任何可读电子邮件地址的ViewState

如果性能很重要,创建过滤HTML时使用StringBuilderIndexOf(等等)而不是拆分行并LINQ结果可能更好 :)

编辑:

为了进一步减少Regex需要检查的字符串长度,您可以仅包括以@字符开头的行,如下所示:

string filteredHtml = String.Join(Environment.NewLine, 
    rawHtmlLines.Where(line => line.IndexOf('@') >= 0 && !line.Contains("_VIEWSTATE")).ToArray());

谢谢,性能并不是那么重要,但我会尝试你的方法来排除“_VIEWSTATE”和类似的行 :) - Johan Helsén
@Johan Helsén:请看我的编辑。我非常确定您正在检查的字符串长度是问题所在。我怀疑您的原始代码最终也会完成,但由于模式非常宽松,因此在如此大的字符串中需要做很多工作。 - SirViver
谢谢!这个很好用。我测试了你的第一个解决方案,去掉了_VIEMSTATE行,并在regex.matches()中添加了RegexOptions.IgnorePatternWhitespace,它运行得很好 :) - Johan Helsén

0
从“函数评估超时”来看,我假设您是在调试器中执行此操作。调试器对于方法执行的时间有一些相当快速的超时限制。并非所有事情都发生得很快。我建议在代码中执行操作,存储结果,然后在调试器中查看该结果(即让Matches运行调用,并在其后设置断点)。
现在,关于检测字符串是否会使Matches花费很长时间;这是一种黑科技。您基本上必须执行某种输入验证。仅因为您从互联网上获得了某个值,并不意味着该值将与Matches很好地配合使用。最终的验证逻辑由您决定;但是,从rawHtmlLines的长度开始可能会有所帮助。(即如果长度为1000000字节,则Matches可能需要一段时间)但是,您必须决定如果长度太长要怎么做;例如向用户提供错误信息。

谢谢,我会尝试将大字符串分成较小的部分并进行匹配。 - Johan Helsén

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接