10

我正在(用 C# 编写)一个简单的解析器来处理一种看起来很像经典 C 的脚本语言。

在我拥有的一个脚本文件中,我用来识别 /* 块注释 */ 的正则表达式正在进入某种无限循环,占用 100% 的 CPU 时间。

我正在使用的正则表达式是这样的:

/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/

关于为什么这可能会被锁定的任何建议?

或者,我可以使用什么其他正则表达式来代替?

更多信息:

  • 在面向 .NET 3.5 的 C# 3.0 中工作;
  • 我正在使用 Regex.Match(string,int) 方法在字符串的特定索引处开始匹配;
  • 我已经让程序运行了一个多小时,但比赛还没有完成;
  • 传递给 Regex 构造函数的选项是RegexOptions.Multilineand RegexOptions.IgnorePatternWhitespace;
  • 正则表达式适用于我的 453 个测试文件中的 452 个。
4

5 回答 5

18

我在您的正则表达式中看到的一些问题:

您的正则表达式中不需要|[\r\n]序列;否定字符类 like[^*]匹配除 之外的所有内容*,包括行分隔符。只有.(点)元字符与这些元字符不匹配。

进入评论后,您唯一需要寻找的字符就是星号;只要您没有看到其中之一,您就可以吞噬任意数量的角色。[^*]这意味着当你可以使用时使用它是没有意义的[^*]+。事实上,你不妨把它放在一个原子组中(?>[^*]+)——因为一旦你匹配了这些非星号,你将永远没有任何理由放弃它们。

过滤掉多余的垃圾,最外层括号内的最后一个选择是\*+[^*/],这意味着“一个或多个星号,后跟一个不是星号或斜杠的字符”。这将始终与注释末尾的星号匹配,并且总是不得不再次放弃它,因为下一个字符是斜杠。事实上,如果在最后一个斜线之前有 20 个星号,那么你的正则表达式的那部分将匹配它们,然后它会一个接一个地放弃它们。然后最后一部分--\*+/将匹配它们以保持不变。

为了获得最佳性能,我会使用这个正则表达式:

/\*(?>(?:(?>[^*]+)|\*(?!/))*)\*/

这将很快匹配格式良好的评论,但更重要的是,如果它开始匹配不是有效评论的内容,它将尽快失败。


David提供,这是一个将嵌套注释与任何嵌套级别匹配的版本:

(?s)/\*(?>/\*(?<LEVEL>)|\*/(?<-LEVEL>)|(?!/\*|\*/).)+(?(LEVEL)(?!))\*/

它使用 .NET 的平衡组,因此它不会以任何其他方式工作。为了完整起见,这是另一个版本(来自 RegexBuddy 的库),它使用 Perl、PCRE 和 Oniguruma/Onigmo 支持的递归组语法:

/\*(?>[^*/]+|\*[^/]|/[^*])*(?>(?R)(?>[^*/]+|\*[^/]|/[^*])*)*\*/
于 2009-01-20T22:06:10.847 回答
15

不不不!没有其他人读过掌握正则表达式(第 3 版)!?在这篇文章中,Jeffrey Friedl 研究了这个确切的问题并将其用作示例(第 272-276 页)来说明他的“展开循环”技术。他对大多数正则表达式引擎的解决方案是这样的:

/\*[^*]*\*+(?:[^*/][^*]*\*+)*/

然而,如果正则表达式引擎被优化为处理惰性量词(就像 Perl 的那样),那么最有效的表达式要简单得多(如上面所建议的):

/\*.*?\*/

(当然应用了等效的 's' “点匹配所有”修饰符。)请注意,我不使用 .NET,所以我不能说哪个版本对于该引擎来说更快。

于 2010-10-15T20:05:54.293 回答
2

您可能想尝试使用 Singleline 而不是 Multiline 选项,那么您不必担心 \r\n。启用此功能后,以下对我有用的简单测试包括跨越多行的注释:

/\*.*?\*/
于 2009-01-20T20:14:38.700 回答
1

我觉得你的表达方式太复杂了。应用于大字符串,许多替代方案意味着很多回溯。我想这是你看到的性能影响的来源。

如果基本假设是匹配从"/*"直到"*/"遇到第一个的所有内容,那么一种方法是这样(像往常一样,正则表达式不适合嵌套结构,因此嵌套块注释不起作用):

/\*(.(?!\*/))*.?\*/             // run this in single line (dotall) mode

从本质上讲,这表示:"/*",后面是任何本身没有跟随的东西"*/",然后是"*/"

或者,您可以使用更简单的:

/\*.*?\*/                       // run this in single line (dotall) mode

像这样的非贪婪匹配有可能在极端情况下出错——目前我想不出这个表达式可能会失败,但我不完全确定。

于 2009-01-20T20:15:33.143 回答
1

我现在正在使用这个

\/\*[\s\S]*?\*\/
于 2014-05-10T23:01:31.140 回答