【问题标题】:Regular expression hangs program (100% CPU usage)正则表达式挂起程序(100% CPU 使用率)
【发布时间】:2012-07-16 09:37:53
【问题描述】:

当我使用下面的字符串作为正则表达式的输入时,Java 以 100% 的 CPU 使用率挂起。

使用的正则表达式:

这是我的应用程序中用于描述字段的正则表达式。

^([A-Za-z0-9\\-\\_\\.\\&\\,]+[\\s]*)+

用于测试的字符串:

Provider_One 的 SaaS 服务 VLAN
Didier SPT 的第二次尝试,因为他给我的第一次是错误的 :-(

当我将相同的字符串拆分为不同的组合时,它可以正常工作。像“Provider_One 的 SaaS 服务 VLAN”、“他给我的第一个是错误的 :-(”等。Java 仅针对上面给定的字符串挂起。

我还尝试如下优化正则表达式。

^([\\w\\-\\.\\&\\,]+[\\s]*)+

即使这样也不行。

【问题讨论】:

  • 你想从那个字符串中匹配或提取什么?您的正则表达式似乎基本上可以匹配任何句子。
  • @user1531484 - 能否请您发布整个代码,即模式、匹配器和要获取的代码。
  • 从字符串中删除笑脸和数字时是否有效?
  • 能否请您发布用于此的 java 代码?
  • 既然有人解释了为什么会发生这种情况,请问您是否尝试过流处理方法?

标签: java regex


【解决方案1】:

首先,您需要意识到您的正则表达式不能匹配提供的输入字符串。字符串包含许多不是“单词”字符的字符('<' '>' '/' ':'')')。

那为什么要花这么长时间?

基本上是“灾难性的回溯”。更具体地说,您的正则表达式的重复结构为正则表达式回溯算法提供了指数数量的备选方案!

你的正则表达式是这样说的:

  1. 一个或多个单词字符
  2. 后跟零个或多个空格字符
  3. 根据需要多次重复前面的 2 个模式。

问题在于“零个或多个空格字符”部分。第一次,匹配器会将所有内容匹配到第一个意外字符(即'<')。然后它会后退一点,然后用不同的替代方案重试...在最后一个字母之前涉及“零空格”,然后当失败时,它将“零空格”移回一个位置。

问题在于,对于带有N 非空格字符的字符串,N 可以匹配“零空格”的不同位置,这使得2^N 有不同的组合。随着N 的增长,这迅速变成了一个巨大的数字,最终结果很难与无限循环区分开来。

【讨论】:

    【解决方案2】:

    catastrophic backtracking的又一个经典案例。

    当正则表达式到达不属于字符类的输入字符串中的: 时,您有嵌套的量词会导致检查大量排列(假设您使用的是.matches() 方法) .

    让我们将问题简化为这个正则表达式:

    ^([^:]+)+$
    

    还有这个字符串:

    1234:
    

    正则引擎需要检查

    1234    # no repetition of the capturing group
    123 4   # first repetition of the group: 123; second repetition: 4
    12 34   # etc.
    12 3 4 
    1 234
    1 23 4
    1 2 34
    1 2 3 4
    

    ...这只是四个字符。在您的示例字符串中,RegexBuddy 在 100 万次尝试后中止。 Java 会很高兴地继续努力……在最终承认这些组合都不允许以下 : 匹配之前。

    你怎么能解决这个问题?

    您可以使用possessive quantifiers 禁止正则表达式回溯:

    ^([A-Za-z0-9_.&,-]++\\s*+)+
    

    将允许正则表达式更快地失败。顺便说一句,我删除了所有不必要的反斜杠。

    编辑:

    一些测量结果:

    在字符串 "was wrong :-)" 上,RegexBuddy 需要 862 步才能找出不匹配的内容。
    对于"me was wrong :-)",是 1,742 步。
    对于"gave me was wrong :-)",14,014 步。
    对于"he gave me was wrong :-)",28,046 步。
    对于"one he gave me was wrong :-)",112,222 步。
    对于"first one he gave me was wrong :-)",>1,000,000 步。

    【讨论】:

    • . 需要保留反斜杠。
    • @Thor84no:不。在字符类中,点表示点。
    • 不必要的反斜杠对代码无害,但对眼睛有害。
    • 所有格量词禁止回溯。 “如果你走到这一步,并且有部分比赛,你就不能回去了。如果你失败了,那就放弃吧。”
    • @user1531484: Tripleee 已经回答了你的问题;我添加了另一个链接到正则表达式大师 Jan Goyvaerts 的正则表达式教程(关于所有格量词的部分)。我强烈推荐整个教程。
    【解决方案3】:

    你为什么将空格与其他字符分开匹配?为什么你在开始时锚定比赛,而不是在结束时?如果你想确保字符串不以空格开头或结尾,你应该这样做:

    ^[A-Za-z0-9_.&,-]+(?:\s+[A-Za-z0-9_.&,-]+)*$
    

    现在正则表达式引擎只有一个“路径”可以通过字符串。如果它在到达结尾之前用完了匹配[A-Za-z0-9_.&,-] 的字符,并且下一个字符不匹配\s,它会立即失败。如果它在仍然匹配空白字符的情况下到达末尾,则会失败,因为每次运行空白字符后都需要匹配至少一个非空白字符。

    如果您想确保只有一个空格字符分隔非空格的运行,只需从 \s+ 中删除量词:

    ^[A-Za-z0-9_.&,-]+(?:\s[A-Za-z0-9_.&,-]+)*$
    

    如果您不关心空白与非空白的关系,只需将它们与相同的字符类匹配即可:

    ^[A-Za-z0-9_.&,\s-]+$
    

    我假设您知道您的正则表达式与给定的输入不匹配,因为笑脸中有 :(,您只是想知道为什么需要这么长时间才能失败。

    当然,由于您正在以 Java 字符串文字的形式创建正则表达式,您可以编写:

    "^[A-Za-z0-9_.&,-]+(?:\\s+[A-Za-z0-9_.&,-]+)*$"
    

    "^[A-Za-z0-9_.&,-]+(?:\\s[A-Za-z0-9_.&,-]+)*$"
    

    "^[A-Za-z0-9_.&,\\s-]+$"
    

    (我知道您在原始问题中有双反斜杠,但这可能只是为了让它们正确显示,因为您没有使用 SO 出色的代码格式化功能。)

    【讨论】:

    • "^[A-Za-z0-9_.&,-]+(?:\\s[A-Za-z0-9_.&,-]+)*$" 没有匹配与原始字符串相同的字符串,因为它不会匹配具有两个连续空格的字符串。但是你的正则表达式 "^[A-Za-z0-9_.&,-]+(?:\\s+[A-Za-z0-9_.&,-]+)*$" 确实,我更喜欢它比 Tim Pietzcker 的所有格量词解决方案 - 后者太聪明了。 :-)
    猜你喜欢
    • 2011-01-25
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多