我有一些进程,它运行在一些包含大量奇怪数据的文件上。 该过程需要找到一些字符串并用其他东西替换它。这是功能:
private static string ReplaceRegex(string inputText, string regex, string replacement)
{
return (replacement != null)?new Regex(regex, RegexOptions.IgnoreCase).Replace(inputText, replacement).Trim(): string.Empty;
}
它在大多数情况下都能正常工作,但是一旦我向这个函数传递了一个具有3491个字符长度的inputText,并且这个字符串作为正则表达式:
"\[HYPERLINK\]\s*(?:\<[\s\S]*?\>)*\s*([\s\S]*?)\s*(?:\<[\s\S]*?\>)*\s*\[\/HYPERLINK]\s*(?:\<NO1\>)?\s*(?:\<WC1?\>)?\s*\[URL\]\s*(?:\<NO1?\>)?\s*(?:\<WC1?\>)?\s*([\s\S]*?)\s*(?:\<NO1?\>)?\s*(?:\<WC1?\>)?\s*\[\/URL\](?:\<NO1?\>)?(?:\<WC1?\>)?"
这个过程停滞不前。
我在等待该系统会抛出OutOfMemory异常,但它没有,它只是卡住了。我等待它响应了好几个小时,但它没有回复。
我有什么想法可以解决这个问题吗?
编辑:感谢大家。
说实话,我在项目中继承了这段代码,现在正试图找出正在发生的事情。而且我不知道为什么有人这样做了。
答案 0 :(得分:3)
基本上,当你有一个可变长度的表达式(*
,+
等)后跟一个“重叠”(也就是说,两个表达式都可以在同一组字符上匹配)变长表达式,你可以在两个表达式之间进行拉锯战。这通常只发生在整个表达式失败并且.NET正则表达式尝试在重叠表达式之间移动输入文本时,通常会在测试中错过。
你的表达式有许多可能导致这种情况的子表达式,但这是一个例子:
\s*([\s\S]*?)
第一部分\s*
可以匹配零个或多个空格字符。第二个[\s\S]*?
也可以匹配零个或多个空白字符(除了非空格字符)。如果您的输入在第一次尝试失败并且有多个空格字符匹配,这将导致灾难性的回溯。
我在这里也写了一些关于这个问题的文章:
How can I recognize an evil regex?
答案 1 :(得分:2)
问题几乎肯定是回溯。正则表达式 greedy 。一般规则是采取“最左边最长”的比赛。像.*Foo.*Bar.*
这样的东西很贪婪:
.*
将使用整个源文本。Foo
之后没有,它会开始回溯,缩短匹配,直到它有Foo
。.*
将再次从源文本中的当前点开始逐渐消失。Bar
。所以它再次回溯,直到找到Bar
。此时您应该注意,如果找不到Bar
,则回溯会继续回来,寻找另一个Foo
。
你可以想象一个由复杂的正则表达式创建的组合爆炸,带有大量的回溯。
最终.*
将消耗从该点到字符串结尾的所有内容。
因此...
获取Jeffrey Fried的 opus ,Mastering Regular Expressions
它会帮助你非常。
答案 2 :(得分:1)
[\s\S]*?
变成了一个贪婪的怪物:
\[HYPERLINK\]
( [\s\S]*? ) # This turns into a greedy monster
\[\/HYPERLINK\] # as soon as one of <- this
\s*
(?: \<NO1\> )?
\s*
(?: \<WC1?\> )?
\s*
\[URL\] # or <- this
( [\s\S]*? ) # This turns into a greedy monster of a greedy monster
\[\/URL\] # or <- this are missing
编辑:您可以通过下面的内容来解决这个问题,但如果限制太多,您至少需要一些中间表达锚点才能将其分解。
# \[HYPERLINK\]\s*(?:\<[^>]*\>)*\s*((?:(?!\[\/HYPERLINK\]|\<[^>]*\>)[\S\s])*)\s*(?:\<[^>]*\>)*\s*\[\/HYPERLINK\]\s*(?:\<NO1\>)?\s*(?:\<WC1?\>)?\s*\[URL\]\s*(?:\<NO1?\>)?\s*(?:\<WC1?\>)?\s*((?:(?!\[\/URL\]|\<[^>]*\>)[\S\s])*)\s*(?:\<NO1?\>)?\s*(?:\<WC1?\>)?\s*\[\/URL\](?:\<NO1?\>)?(?:\<WC1?\>)?
\[ HYPERLINK \]
\s*
(?: \< [^>]* \> )*
\s*
(
(?:
(?! \[\/HYPERLINK \] | \< [^>]* \> )
[\S\s]
)*
)
\s*
(?: \< [^>]* \> )*
\s*
\[\/HYPERLINK\]
\s*
(?: \<NO1\> )?
\s*
(?: \<WC1?\> )?
\s*
\[URL\]
\s*
(?: \<NO1?\> )?
\s*
(?: \<WC1?\> )?
\s*
(
(?:
(?! \[\/URL\] | \< [^>]* \> )
[\S\s]
)*
)
\s*
(?: \<NO1?\> )?
\s*
(?: \<WC1?\> )?
\s*
\[\/URL\]
(?: \<NO1?\> )?
(?: \<WC1?\> )?
答案 3 :(得分:0)
这可能与您使用大量*
的事实有关。创建一个可以通过占用所有资源来削弱系统的正则表达式非常容易,特别是在创建如此大的资源时。
就个人而言,我会尝试在那里添加一些限制(如.{1,100}
)。
答案 4 :(得分:0)
我对(?:\<[\s\S]?>)
和类似的重复模式感兴趣。 [\s\S]?
匹配“一个空格或非空白字符”。我认为这在功能上等同于正则表达式.
你还有像\s*([\s\S]*?)\s*
这样的东西,它们是“零个或多个空格,后跟零个或多个(空格或非空格),后跟零个或多个空格”。这在技术上是合法的,但在实际意义上却很奇怪。
即使“它有效”,我建议你以一种可以维护的方式重写你的正则表达式,或者找到一种不使用正则表达式来解析字符串的方法。
答案 5 :(得分:0)
使用*
(零或更多)退出,并在知道将有数据存在时使用+
(一个或多个)为解析器提供更好的处理提示 。仅在或类型的情况下使用*
,您不希望它失败并且不允许包含任何内容。