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

标签 java regex

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

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

如果我减少字符串开头的点数,则执行时间会缩短(似乎呈指数增长)。这是挂起线程的堆栈跟踪:

[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

为什么会这样?

最佳答案

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

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

有人可能不了解正则表达式或不关心性能并抛出 .* 他需要字符串中某处匹配的地方并且引擎来回执行步骤的速度如此之快以至于什么都没有除非找不到模式,否则看起来很奇怪或很慢。

时间复杂度从 O(n) 开始,一直到 O(n^2b),其中 b 是嵌套量词的级别。所以在失败时引擎采取的步骤数是巨大的。

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

  • 指定边界。如果模式应该在数字之前的某处停止,请不要执行 .*。而是执行 \D*

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

  • 使用所有格量词或原子组(如果可用)。这两个避免回溯。有时您不需要回溯。

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

您自己的正则表达式也不异常(exception)。它有很多贪婪和可选匹配,需要时间重新研究。

关于java - 为什么这个正则表达式需要很长时间才能执行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50510690/

相关文章:

java - 为什么我的 ArrayList 包含添加到列表中的最后一项的 N 个副本?

ruby-on-rails - 如何从 Rails 中的字符串中识别一组日期

regex - 在 shell 中使用 grep 时,PCRE 正则表达式似乎不起作用

arrays - 获取特定格式的子字符串

java - 如何确保用户在输入无效内容后可以再次输入?

java - Arraylist 删除第一次出现,而不是一个确切的对象

java - Window Batch 文件集成到 Java GUI 中

java - 从 CSV raw 插入 SQLite 的更快方法?

正则表达式获取匹配字符串后的单词

regex - Vim:当使用\_ 跨多行匹配字符串时。在正则表达式中,:yank 命令仅适用于第一行