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

Jul*_*lo 5 java regex linux

StackOverflowError当我使用以下Reg Ex时,我得到了:

"([A-Z][A-Z]\\d\\d[A-Z]\\[(\\*|(((\\d|\\d\\d)-(\\d|\\d\\d))|(\\d|\\d\\d)))\\](,|$))+";
Run Code Online (Sandbox Code Playgroud)

匹配这样的东西String:

RA01D[1-1],RA01D[17-17],RA01D[2-2],RA01D[18-18]
Run Code Online (Sandbox Code Playgroud)

nha*_*tdh 5

斯特里比泽夫的回答指出并修复了正则表达式的低效率。这里不存在灾难性的回溯。该更改仅稍微延迟了问题,StackOverflowError但并未解决问题(请参阅附录)。

在原来的正则表达式中,如果第一个分支(\d|\d\d)-(\d|\d\d)失败,第二个分支会做额外的工作(\d|\d\d)再次匹配,这是第一个分支的前缀。

(
  (
    (\d|\d\d)-(\d|\d\d)
  )
  |
  (\d|\d\d)
)
Run Code Online (Sandbox Code Playgroud)

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

(\d|\d\d)(?:-(\d|\d\d))?
Run Code Online (Sandbox Code Playgroud)

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


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

由于正则表达式是明确的,因此您可以通过使最外层组上的量词具有所有格来解决问题。正则表达式是从 stribishev 的答案复制的,并进行了一些修改:

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

由于该实现使用循环来实现所有格量词(因为不需要回溯),因此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();

        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Wik*_*żew 2

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

 (
   (
    (\d|\d\d)-(\d|\d\d)
   )
   |
   (\d|\d\d)
 )
Run Code Online (Sandbox Code Playgroud)

它等于

(
   (
    (\d|\d\d)(?:-(\d|\d\d))?
   )
)
Run Code Online (Sandbox Code Playgroud)

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

所以,尝试使用这个正则表达式(请参阅此处的演示

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

或者作为 Java 字符串:

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

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