【问题标题】:Regex causing abnormally high CPU use in C#正则表达式导致 C# 中 CPU 使用率异常高
【发布时间】:2013-06-30 03:15:04
【问题描述】:

在最近阅读了一种被称为“灾难性回溯”的现象后,似乎我自己的正则表达式模式正在导致某种 CPU 问题。我使用这个表达式来扫描 100k-200k 个字符的大型 HTML 字符串。该模式匹配 IP:port 格式的 IP 地址(例如 1.1.1.1:90)。模式如下:

private static Regex regIp = new Regex(@"(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\." +
        @"(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4]" +
        @"[0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}" +
        @"[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])\:[0-9]{1,5}", 
        RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.CultureInvariant);

表达式使用如下:

MatchCollection matchCol = regIp.Matches(response);

foreach (Match m in matchCol)
{ 
    doWorkWithMatch(m);
}

通过这个正则表达式模式运行大约 100 个字符串后,它开始阻塞计算机并使用 99% 的 CPU。有没有更合乎逻辑的方式来构造这个表达式以减少 CPU 使用率并避免回溯?我不确定是否会发生回溯,或者是否只是同时执行正则表达式评估的线程过多的问题 - 欢迎所有输入。

【问题讨论】:

  • 不要试图用 Regex 解析像 HTML 这样的不规则语言 - 这就是疯狂的方式。不要这样做。永远不会。寻找另一种方式。加入修道院。拿起钩针。除了这个!
  • 用正则表达式解析 HTML 是不可能的,除非在非常特殊的情况下。我只是建议你找到一种不同的方法,所以如果将 HTML 解析为文档不适合你,请使用不同的方法。
  • 看看Html Agility Pack - 我从未使用过它,但我在尝试使用 .NET 解析 HTML 时曾多次看到它推荐。
  • @Tim 给定 op 想要匹配的模式,正则表达式是正确的选择
  • 正则表达式非常适合这个应用程序,因为您根本不关心语言的结构。如果您的搜索要求依赖于 html 的结构,Mike 的观点是有效的。语言在您的应用程序中毫无意义。

标签: c# .net regex


【解决方案1】:

为什么要使用正则表达式进行解析和验证

你应该使用这个正则表达式来解析字符串

\d+[.]\d+[.]\d+[.]\d+(:\d+)?

然后您可以通过将 IP 地址解析为 int 然后检查范围来检查该 IP 地址是否具有有效范围

【讨论】:

    【解决方案2】:

    这是我用于测试和验证 IP 地址的正则表达式

    我在最后添加了你的端口测试:

    (?:(?:1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9][0-9]|[0-9])\.){3}(?:1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9][0-9]|[0-9]):[0-9]{1,5}

    我看到您还捕获了所有单独的八位字节,您将通过使用非捕获 (?:...) 语法获得性能提升,然后只需将经过验证的字符串拆分为非数字。

    【讨论】:

    • 虽然这不是问题的根本原因,但这种特殊的正则表达式模式确实为最初发布的表达式提供了显着的性能改进。谢谢!你是如何为正则表达式生成漂亮的图形/视觉的?!手头的真正问题是试图将一个 300k 字符的 XML 文档加载到正则表达式中以匹配 HTML 锚标记 - 我不知道是因为它是 XML 还是因为它是 300k 字符使 CPU 失灵,但它阻塞了程序。
    • 酷我很高兴这有帮助。为了制作该图表,我正在使用 debuggex.com。尽管它不支持lookbehinds、命名捕获组或原子组,但它对于理解表达式流仍然很方便。还有 regexper.com。它们也做得很好,但在您输入时并不是实时的。
    【解决方案3】:

    这个正则表达式看起来设计得很好,如果你想要 100% 的准确率,我看不出有什么地方可以改进它。但是,您可以测试一些可能总是有效的更简单的方法是否可以改善结果。

    \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}
    

    显然,这可能会捕获一些不正确的内容,例如 999.999.999.999:999。但是你必须问问自己是否会出现这样的不切实际的输入。如果这确实提高了性能,并且您有理由确定您不会像我的示例那样有疯狂的输入,那么使用它并使用您更准确的正则表达式来剔除列表。

    【讨论】:

    • 这个表达式实际上比 Denomale 下面列出的答案要慢,而且它需要进一步验证 IP 地址。谢谢你的努力,但没有雪茄。
    • @user1111380 感谢您的反馈。实际上,我很惊讶这变慢了。我必须对此进行进一步调查,因为我很想知道原因。祝你好运!
    【解决方案4】:

    也许您可以对结果进行分组并稍后对其进行测试,使其端口不超过 255 和 65535,即像 Daniel Gimenez 的回答,但使用组 (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\:(\d{1,5}),然后对匹配的组进行测试。

    在正则表达式中包含这么多| 通常是个坏主意。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-21
      • 2012-07-16
      • 1970-01-01
      相关资源
      最近更新 更多