大字符串的长正则表达式

时间:2013-09-20 15:51:25

标签: c# regex out-of-memory large-files

我有一些进程,它运行在一些包含大量奇怪数据的文件上。 该过程需要找到一些字符串并用其他东西替换它。这是功能:

 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异常,但它没有,它只是卡住了。我等待它响应了好几个小时,但它没有回复。

我有什么想法可以解决这个问题吗?

编辑:感谢大家。

说实话,我在项目中继承了这段代码,现在正试图找出正在发生的事情。而且我不知道为什么有人这样做了。

6 个答案:

答案 0 :(得分:3)

你有所谓的“catastrophic backtracking”。

基本上,当你有一个可变长度的表达式(*+等)后跟一个“重叠”(也就是说,两个表达式都可以在同一组字符上匹配)变长表达式,你可以在两个表达式之间进行拉锯战。这通常只发生在整个表达式失败并且.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

Master Regular Expressions Cover

它会帮助你非常

答案 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)

使用*(零或更多)退出,并在知道将有数据存在时使用+(一个或多个)为解析器提供更好的处理提示 。仅在类型的情况下使用*,您不希望它失败并且不允许包含任何内容。