多行字符串文字的正则表达式产生`StackOverflowError`

时间:2018-06-05 22:57:25

标签: java regex scala stack-overflow

我希望匹配三"个括号中的字符串 - 可能包含换行符的引号,并且除了最开头和最后一行之外不包含任何""" - 子字符串。< / p>

有效示例:

"""foo
bar "baz" blah"""

无效示例:

"""foo bar """ baz"""

我尝试使用以下正则表达式(作为Java String文字):

"(?m)\"\"\"(?:[^\"]|(?:\"[^\"])|(?:\"\"[^\"]))*\"\"\""

它似乎适用于简短的例子。但是,在较长的示例中,例如在包含hello world的千行的字符串上,它会给我一个StackOverflowError

Scala片段重现错误

import java.util.regex.{Pattern, Matcher}

val text = "\"" * 3 + "hello world \n" * 1000 + "\"" * 3
val p = Pattern.compile("(?m)\"\"\"(?:[^\"]|(?:\"[^\"])|(?:\"\"[^\"]))*\"\"\"")
println(p.matcher("\"\"\" foo bar baz \n baz bar foo \"\"\"").lookingAt())
println(p.matcher(text).lookingAt())

(注意:在本地测试,Scastie超时;或者可能会减少1000到更小的数字?)。

产生相同错误的Java代码段

import java.util.regex.Pattern;
import java.util.regex.Matcher;

class RegexOverflowMain {
  public static void main(String[] args) {
    StringBuilder bldr = new StringBuilder();
    bldr.append("\"\"\"");
    for (int i = 0; i < 1000; i++) {
      bldr.append("hello world \n");
    }
    bldr.append("\"\"\"");
    String text = bldr.toString();
    Pattern p = Pattern.compile("(?m)\"\"\"(?:[^\"]|(?:\"[^\"])|(?:\"\"[^\"]))*\"\"\"");
    System.out.println(p.matcher("\"\"\" foo bar baz \n baz bar foo \"\"\"").lookingAt());
    System.out.println(p.matcher(text).lookingAt());
  }
}

问题

任何想法如何使这个“堆栈安全”,即有人可以找到一个接受相同语言的正则表达式,但是在提供给Java正则表达式API时不会产生StackOverflowError吗?

我不关心解决方案是使用Scala还是Java(或其他),只要使用相同的底层Java正则表达式库。

2 个答案:

答案 0 :(得分:2)

使用否定前瞻的解决方案基本上找到以"""开头并以"""结尾的字符串,其中包含不包含"""的内容

作为普通正则表达式:^"""((?!""")[\s\S])*"""$

随着Java转义正则表达式:"^\"\"\"((?!\"\"\")[\\s\\S])*\"\"\"$&#34;

\s\S包含换行符(基本上. +换行符或.带有单行标记)

这应该在没有多行标志的情况下使用,以便^$匹配字符串的开头和结尾,而不是行的开头和结尾

否则:

""" ab """abc""" abc """

会匹配

我也将此作为参考,以排除"""Regular expression to match a line that doesn't contain a word?

答案 1 :(得分:2)

下面的完整答案优化了正则表达式的性能,但为了防止堆栈溢出,作为一个简单的解决方案,只需使重复组占有

具有选择权的非占有性重复组需要递归调用以允许回溯。使其具有占有性可以解决问题,因此只需在+之后添加*

"(?m)\"\"\"(?:[^\"]|(?:\"[^\"])|(?:\"\"[^\"]))*+\"\"\""

另请注意,如果您要匹配整个输入,则需要拨打matches(),而不是lookingAt()

提升绩效

注意:快速性能测试表明,这比answer by x4rf41中的正则表达式强<<>> <强> 6倍

而不是匹配其中一个

  • 不是引用:[^\"]
  • 正好一句话:(?:\"[^\"])
  • 正好两个引号:(?:\"\"[^\"])

在一个循环中,首先将所有内容与引用匹配。如果是单引号或双引号,而不是三引号,则匹配1-2引号,然后根据需要重复所有内容。最后匹配结束的三重引用。

这种匹配是确定的,所以使重复占有欲。如果输入有许多嵌入式引号,这也可以防止堆栈溢出。

"{3}          match 3 leading quotes
[^"]*+        match as many non-quotes as possible (if any) {possesive}
(?:           start optional repeating group
   "{1,2}       match 1-2 quotes
   [^"]++       match one or more non-quotes (at least one) {possesive}
)*+           end optional repeating group                  {possesive}
"{3}          match 3 trailing quotes

由于您不使用^$,因此无需(?m)MULTILINE

作为Java字符串:

"\"{3}[^\"]*+(?:\"{1,2}[^\"]++)*+\"{3}"