简化导致Java StackOverflowException的正则表达式

时间:2012-03-25 13:04:06

标签: java regex stack-overflow

我正在尝试从C文件中提取以下项目:

  • 评论(单行和多行)
  • 字符串文字
  • 十进制,八进制和十六进制文字。

我写了以下正则表达式试图找到这些项目:

/\*(?:.|[\r\n])*?\*/|"(?:[^"\\\r\n]|\\.)*"|//.*|\b\d+\b|\b0[xX][\da-fA-F]+\b

表达式由ORed组成的五个部分组成。

  • /\*(?:.|[\r\n])*?\*/检查多行评论。
  • "(?:[^"\\\r\n]|\\.)*"检查字符串文字。
  • //.*检查单行注释。
  • \b\d+\b检查十进制和八进制常量。
  • \b0[xX][\da-fA-F]+\b检查十六进制常量。

虽然使用regexpal和500行C文件测试表达式似乎工作正常,但我的Java程序在几次匹配后抛出StackOverflowException。

以下是使用正则表达式的Java代码:

Pattern itemsOfInterestPattern = Pattern.compile(
        "/\\*(?:.|[\\r\\n])*?\\*/|\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"|//.*|\\b\\d+\\b|\\b0[xX][\\da-fA-F]+\\b");
// Now, go through the source file and process any tags we find
Matcher itemsOfInterestMatcher = itemsOfInterestPattern.matcher(sourceFile);
int matchNumber = 0;
while (itemsOfInterestMatcher.find()) {
    // We've found a token
    ++matchNumber;
    String token = itemsOfInterestMatcher.group();
    // I then have a switch statement that processes each match depending on its type
}

发生溢出时的堆栈跟踪可以在http://pastebin.com/7eL6mVd2

找到

导致堆栈溢出的原因是什么?如何更改表达式以使其工作?

阿姆鲁

2 个答案:

答案 0 :(得分:2)

根据java.util.regex.Pattern$LazyLoop.match(...)出现在堆栈跟踪中的次数判断,我打赌问题是使用不情愿的量词*?:首先它试图匹配任何东西,然后它回溯并尝试匹配一个字符,然后它回溯并尝试匹配两个字符,依此类推。因此,如果你有一个很长的评论,它将不得不做很多回溯,这显然涉及递归。 (我不知道所有回溯是否涉及递归,或者只是不情愿的量词回溯;事实上,直到现在,我甚至都没有意识到不情愿量词的回溯确实。)如果你改变这个部分:

/\*(?:.|[\r\n])*?\*/

到此:

/\*(?:[^*]|\*(?!/))*+\*/

(使用占有量词*+代替 - 它试图尽可能多地匹配,并且永远不会给予任何回报),我想你会发现你可以更好地处理长篇评论。所以,总的来说,你的字符串文字看起来像这样:

"/\\*(?:[^*]|\\*(?!/))*+\\*/|\"(?:[^\"\\\\\\r\\n]|\\\\.)*\"|//.*|\\b\\d+\\b|\\b0[xX][\\da-fA-F]+\\b"

编辑添加(2013年7月):我公司的某位人员最近遇到了类似的问题,这让我对这个问题有了更深入的了解。我发现,问题不仅仅是回溯,而是回溯与子群的结合;例如,a*a*?不会出现此问题,但(a)*(a)*?(?:a)*(?:a)*?会出现此问题。上面,我建议使用*+而不是*?来禁用回溯(并对子表达式进行必要的更改);但另一种方法是通过改变这个来消除子表达式:

/\*(?:.|[\r\n])*?\*/

到此:

/\*(?s:.*?)\*/

(其中(?s:...)表示法等同于...,但它在本地启用MULTILINE模式,这意味着.将匹配任何字符,甚至{{1 }})。 \n不需要递归以启用回溯。

那就是说,我认为.*?方法在这种情况下更好,也许在大多数情况下,因为它的算法时间复杂度较低。 (*+需要不断尝试匹配并重新匹配模式的其余部分;它可以执行任意回溯而不会溢出堆栈,但这可能需要花费过多的时间才能完成。)

答案 1 :(得分:0)

鉴于错误(我们没有看到代码:()重要的提示是,实际答案通常隐藏在堆栈跟踪的前十行中。您需要多次读取它然后检查代码似乎大多数错误都与正则表达式有关,除了前两个:

at java.lang.AbstractStringBuilder.charAt(AbstractStringBuilder.java:173)
at java.lang.StringBuilder.charAt(StringBuilder.java:55)

恕我直言,您应该检查这两行(可能使用调试器)。其他帖子说,当你的内存不足时,可能会抛出这样的错误 - How to validate big xml against xsd schema?。尝试使用较少的注释较小的文件,并检查是否仍然出现此错误。