java.util.regex.Pattern上的java.lang.StackOverflowError $ BmpCharProperty.match(Pattern.java:3715)

时间:2015-04-20 21:33:21

标签: java regex linux

我使用时收到StackOverflowError 遵循Reg Ex:

"([A-Z][A-Z]\\d\\d[A-Z]\\[(\\*|(((\\d|\\d\\d)-(\\d|\\d\\d))|(\\d|\\d\\d)))\\](,|$))+";

匹配类似String的内容:

RA01D[1-1],RA01D[17-17],RA01D[2-2],RA01D[18-18]

2 个答案:

答案 0 :(得分:4)

stribizhev's answer指出并修复的是正则表达式中的低效率。这里没有灾难性的回溯。此更改只会稍微延迟StackOverflowError而不解决它(请参阅附录)。

在原始正则表达式中,如果第一个分支(\d|\d\d)-(\d|\d\d)失败,第二个分支将再次匹配(\d|\d\d),这是第一个分支的前缀。

(
  (
    (\d|\d\d)-(\d|\d\d)
  )
  |
  (\d|\d\d)
)

重写时(如他的回答所示),前缀(\d|\d\d)只会匹配一次,引擎只需要检查2个不同的续集(匹配-(\d|\d\d)或只是空字符串)。

(\d|\d\d)(?:-(\d|\d\d))?

这就是他的答案如何提高正则表达式的效率。同样的方法适用于\d|\d\d


回到StackOverflowError的问题。如果在包含1000个元素的字符串上运行正则表达式,则上述任何正则表达式都将导致StackOverflowError。这是由于Sun / Oracle / OpenJDK中的Pattern类的实现,它使用递归来进行贪婪和惰性量词。

由于正则表达式非模糊,您可以通过使最外层组的量词占有率来解决问题。正则表达式是从stribizhev的答案中复制而来的,但有一些修改:

"(?:[A-Z][A-Z]\\d\\d[A-Z]\\[(?:\\*|\\d{1,2}(?:-\\d{1,2})?)\\](?:,|$))++"
                                                                     ^^

由于实现使用循环来实现占有量词(因为不需要回溯),所以无论输入字符串有多长,都不会发生StackOverflowError。堆栈使用量只有一次重复,与问题中的情况相反,它与字符串中元素的数量线性增长。

附录

测试程序

下面是一个测试程序,显示了正则表达式可以处理的元素数量。在我的系统(Oracle JRE,版本1.8.0_25)中,问题中的正则表达式只能在崩溃之前达到104 * 4 = 416个元素,stribizhev的答案设法达到137 * 4 = 548,stribizhev'修改了答案以删除不必要的组管理达到150 * 4 = 600,具有所有权量词的版本通过所有测试(500 * 4 = 2000元素)。

public class SO29758814 {
    public static void main(String[] args) {
        String s = "";
        for (int i = 1; i <= 500; i++) {

            s += "RA01D[1-1],RA01D[17-17],RA01D[2-2],RA01D[18-18],";

            System.out.print(i);

            try {
                // Question
                System.out.print(" 1 " + s.matches("([A-Z][A-Z]\\d\\d[A-Z]\\[(\\*|(((\\d|\\d\\d)-(\\d|\\d\\d))|(\\d|\\d\\d)))\\](,|$))+"));
            } catch (Throwable e) { }

            try {
                // stribizhev's answer
                System.out.print(" 2 " + s.matches("([A-Z]{2}\\d{2}[A-Z]\\[(\\*|((\\d{1,2})(?:-(\\d{1,2}))?))\\](?:,|$))+"));
            } catch (Throwable e) { }

            try {
                // stribizhev's answer, remove unnecessary groups
                System.out.print(" 3 " + s.matches("(?:[A-Z][A-Z]\\d\\d[A-Z]\\[(?:\\*|\\d{1,2}(?:-\\d{1,2})?)\\](?:,|$))+"));
            } catch (Throwable e) { }

            try {
                // stribizhev's answer, remove unnecessary groups, use possessive quantifier
                System.out.print(" 4 " + s.matches("(?:[A-Z][A-Z]\\d\\d[A-Z]\\[(?:\\*|\\d{1,2}(?:-\\d{1,2})?)\\](?:,|$))++"));
            } catch (Throwable e) { }

            System.out.println();

        }
    }
}

答案 1 :(得分:1)

Your regex包含具有类似模式的替代列表,这些模式通常会导致灾难性的回溯并可能影响性能。看看这种模式:

 (
   (
    (\d|\d\d)-(\d|\d\d)
   )
   |
   (\d|\d\d)
 )

等于

(
   (
    (\d|\d\d)(?:-(\d|\d\d))?
   )
)

此外,您最好使用量词,(\d|\d\d)等于\d{1,2}。 我还怀疑你需要捕获一个逗号或字符串结尾,所以添加一个非捕获组(?:,|$)

因此,请尝试使用此正则表达式(see demo here

([A-Z]{2}\d{2}[A-Z]\[(\*|((\d{1,2})(?:-(\d{1,2}))?))\](?:,|$))+

或者作为Java字符串:

String pattern = "([A-Z]{2}\\d{2}[A-Z]\\[(\\*|((\\d{1,2})(?:-(\\d{1,2}))?))\\](?:,|$))+";

您还可以调整捕获组的数量。