使用RegEx匹配大输入时的StackOverflowError

时间:2013-02-26 05:29:28

标签: java regex stack-overflow lookahead

使用RegEx模式匹配结果时得到StackOverflowError

模式为(\d\*?(;(?=\d))?)+。此正则表达式用于验证输入:

12345;4342;234*;123*;344324

输入是由;分隔的值(仅数字)组成的字符串。每个值最后可以包含一个*(用作其他匹配的通配符)。字符串末尾没有;

问题是这个正则表达式工作正常,少数值。但是当值的数量太大(超过300)时,它将导致StackOverflowError

final String TEST_REGEX = "(\\d\\*?(;(?=\\d))?)+";

// Generate string
StringBuilder builder = new StringBuilder();
int number = 123456;
for (int count = 1; count <= 300; count++) {
    builder.append(Integer.toString(number).concat(";"));
    number++;
}
builder.deleteCharAt(builder.lastIndexOf(";"))

builder.toString().matches(TEST_REGEX); //<---------- StackOverflowError

堆栈跟踪:

java.lang.StackOverflowError
    at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3715)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4683)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615)
    at java.util.regex.Pattern$Ques.match(Pattern.java:4079)
    at java.util.regex.Pattern$Ques.match(Pattern.java:4079)
    at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3715)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4683)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615)
    at java.util.regex.Pattern$Ques.match(Pattern.java:4079)
    at java.util.regex.Pattern$Ques.match(Pattern.java:4079)
    at java.util.regex.Pattern$BmpCharProperty.match(Pattern.java:3715)
    at java.util.regex.Pattern$GroupHead.match(Pattern.java:4556)
    at java.util.regex.Pattern$Loop.match(Pattern.java:4683)
    at java.util.regex.Pattern$GroupTail.match(Pattern.java:4615)
    ...

我认为模式中的前瞻会导致此错误,因为有很多查找但我还没有弄清楚如何减少它或解决方法。

我非常感谢任何建议,因为我在RegEx没有经验。

3 个答案:

答案 0 :(得分:7)

在使用StackOverflowError ...

解决问题之前
  1. 我想指出您当前的正则表达式(\d\*?(;(?=\d))?)+无法验证此情况。

      

    每个值最后可以包含一个*(用作其他匹配的通配符)

    它无法拒绝案例23*4*4*;34*434*34,如here 1 所示。

  2. 您的正则表达式会对不匹配的输入进行不必要的回溯。

  3. Java对组(\d\*?(;(?=\d))?)的每次重复使用一个堆栈帧(重复1次或更多次+)。

  4. 正确的正则表达式是:

    \d+\*?(?:;\d+\*?)*
    

    请注意,这会拒绝*,这不是您要求接受还是拒绝的要求。

    这不能解决StackOverflow问题,因为组(?:;\d+\*?)的每次重复也将用尽堆栈。要解决这个问题,请使所有量词占有,因为不需要回溯,因为语法不明确:

    \d++\*?+(?:;\d++\*?+)*+
    

    放入字符串文字:

    "\\d++\\*?+(?:;\\d++\\*?+)*+"
    

    我已经使用匹配和非匹配输入测试了上面的正则表达式,该输入有超过3600个令牌(由;分隔)。

    <强>脚注

    1 :regex101使用PCRE风味,与Java正则表达风味略有不同。但是,正则表达式中使用的功能在它们之间是通用的,因此不应存在差异。

    <强>附录

    • 实际上,根据您的正则表达式(\d\*?(;(?=\d))?)+进行测试(根据您的要求不正确),使得最外层+占有++似乎解决了StackOverflowError问题,至少在我的测试中有大约3600个令牌(由;分隔,字符串大约是20k字符长)。在针对不匹配的字符串进行测试时,它似乎也不会导致执行时间过长。

    • 在我的解决方案中,为群组*所有权设置(?:;\d+\*?)量词就足以解决StackOverflowError

      "\\d+\\*?(?:;\\d+\\*?)*+"
      

      然而,为了安全起见,我将所有的一切都占有欲。

答案 1 :(得分:1)

你的regexp有点无效,与你的描述不符。你有'\ d \ *?' - 它是一个数字,由可选*组成。然后可选';(?= \ d)' - ';'与前瞻数字。字符串'1 * 2 * 3 *'将匹配您的正则表达式,但不符合您的描述。 你可以使用follow regexp。它匹配您的输入和更多的感觉。

final String TEST_REGEX = "(\\d+\\*?)(?:;\\d+\\*?)+";

当计数&lt;时,它将通过测试。 300但仍然因较大的值而失败。 使用像 indexOf substring 这样的纯字符串操作来验证输入。

答案 2 :(得分:0)

您可能想要做的是增加堆栈的最大大小,使其不会溢出。 You can read about how to do that here.

基本上,您使用-Xss选项启动程序。例如,-Xss4m当我使用-Xss4m启动代码时,您的程序在没有堆栈溢出的情况下运行(它返回true)。