为什么这个正则表达式需要很长时间才能执行?

EaZ*_*ode 6 java regex

我发现,例如这条线的执行时间非常长:

System.out.println(
        ".. .. .. .. .. .. .. .. ..  .. .. .. .. .. .. .. .. .. .. .. .... .. .."
        .matches("(?i)(?:.* )?\\W?([a-z0-9-_\\.]+((?: *)\\.(?: *))+(?:DE))(?:[0-9]{1,5})?")
);
Run Code Online (Sandbox Code Playgroud)

如果我减少字符串开头的点数,则执行时间会降低(看起来像是指数).这是挂起线程的堆栈跟踪:

[Repeating text]...
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4279
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Loop.match(Matcher, int, CharSequence) line: 4785
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4279
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Single(Pattern$BmpCharProperty).match(Matcher, int, CharSequence) line: 3798
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4272
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Loop.match(Matcher, int, CharSequence) line: 4785
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4272
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Single(Pattern$BmpCharProperty).match(Matcher, int, CharSequence) line: 3798
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4279
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Loop.matchInit(Matcher, int, CharSequence) line: 4801
Pattern$Prolog.match(Matcher, int, CharSequence) line: 4741
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4272
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Ques.match(Matcher, int, CharSequence) line: 4182
Pattern$BranchConn.match(Matcher, int, CharSequence) line: 4568
Pattern$GroupTail.match(Matcher, int, CharSequence) line: 4717
Pattern$Single(Pattern$BmpCharProperty).match(Matcher, int, CharSequence) line: 3798
Pattern$Curly.match0(Matcher, int, int, CharSequence) line: 4272
Pattern$Curly.match(Matcher, int, CharSequence) line: 4234
Pattern$GroupHead.match(Matcher, int, CharSequence) line: 4658
Pattern$Branch.match(Matcher, int, CharSequence) line: 4604
Matcher.match(int, int) line: 1270
Matcher.matches() line: 604
Pattern.matches(String, CharSequence) line: 1135
String.matches(String) line: 2121
Main.main(String[]) line: 11
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

rev*_*evo 4

当模式x成为可选时 - 使用?or*量词(或{0,}) - 引擎根据所使用的量词的性质有两条路径可供选择:

  • 消耗然后回溯其他模式(贪婪的情况,即.*, .?
  • 首先不消耗并立即寻找其他模式(懒惰的情况.*?

有人可能不了解正则表达式或不关心性能,并.*在字符串中的某个地方需要匹配时抛出,并且引擎来回执行的速度非常快,除非找不到模式,否则没有任何事情看起来奇怪或缓慢。

时间复杂度从嵌套量词的级别开始并O(n)继续。因此,在发生故障时,发动机采取的步数是巨大的。O(n^2b)b

为了避免这种情况,有人需要考虑一些指导原则:

  • 指定边界。如果模式应该在数字之前的某个地方停止,则不行.*。相反,做\D*.

  • 使用条件。x您可以在运行整个匹配之前使用 Lookahead检查模式/字母是否存在^(?=[^x]*x)。这会导致早期失败。

  • 使用所有格量词或原子组(如果可用)。这两点避免了走回头路。有时你不需要走回头路。

  • 不要做(.*)+或类似的图案。相反,重新考虑您的要求或至少使用原子组(?>.*)+

您自己的正则表达式也不例外。它受到很多贪婪和可选匹配的影响,需要一段时间重新研究。