【问题标题】:Very slow look-behind非常缓慢的后视
【发布时间】:2024-04-26 22:20:02
【问题描述】:

我正在尝试使用 java regex 恢复两个位置

第一个由正则表达式给出:

val r="""(?=(?<=[ ]|^)[^ ]{1,21474836}(?=[ ]|$)(?<=[^A-Z]|^)[A-Z]{1,21474836}(?=[^A-Z]|$))"""

第二个由正则表达式给出

val p="""(?<=(?<=[ ]|^)[^ ]{1,21474836}(?=[ ]|$)(?<=[^A-Z]|^)[A-Z]{1,21474836}(?=[^A-Z]|$))"""

请注意,这两个表达式是相同的,除了第一个“=”在第二个表达式中被替换为“not using neste quantifiers。

我的测试命令如下:

r.findAllMatchIn("a <b/>"*100) //.... some long string of size 600...
p.findAllMatchIn("a <b/>"*100) //.... some long string of size 600...

第一个示例在执行过程中几乎是即时的,而第二个示例需要几十秒。如果我在 REPL 中启动相同的示例,两者都非常快。

这是从哪里来的?如何使第二个表达式更快?

更新:为什么这很重要

请注意,一般情况下,我可以有以下类型的表达式:

[^ ]+[^.]+

我想知道这个正则表达式什么时候可以在给定位置的左边找到,或者什么时候可以结束。 如果我有以下位置的数据:

abc145A
0123456

我希望前一个表达式的末尾匹配位置 1,2,3,4,5 和 6。如果我使用非贪婪重复小丑,那么它将匹配 1,3 和 5。如果我使用贪婪运算符,它只匹配 6。这就是我需要后向断言的原因。或者你会找到我一种方法来定义运算符来找到我正在寻找的位置。

【问题讨论】:

  • 我猜双重后视会导致它反复循环相同的字符 - 花费 O(n^2) 时间而不是 O(n) 时间。
  • 另一种可能性:{1,21474836} 是一个非常非常大的范围,包含它的后视所花费的时间可能与该范围的大小成正比。
  • 我试图将这个数字降低到 2000,但它并没有改变任何东西。
  • 如果减少到 10 会怎样? (Java 可能会自动将其缩短为您正在搜索的字符串的长度。)
  • 将其减少到 10 可将速度提高 4 倍。

标签: java regex performance scala


【解决方案1】:

您没有使用嵌套的量词,但我怀疑嵌套的lookbehinds 会导致类似的问题。我怀疑您根本不需要外部前瞻/后瞻 - 仅使用正则表达式的内部部分(两者通用)执行单个正则表达式搜索,并从每个结果中检索开始位置和结束位置如何?

【讨论】:

  • 我之前已经尝试过这种方法,但并非在所有情况下都有效。事实上,如果我使用单个表达式,它可能会匹配太长的标记,并且会隐藏标记的另一种外观
  • 您可以通过在循环中一次搜索一个匹配项来解决此问题,而不是使用一个方法调用搜索所有匹配项。或者,您可以在第一个正则表达式中放置一个组(在 "[^ ]{1,21474836}" 周围加上括号),并使用它的长度来确定结束位置的位置。