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)
斯特里比泽夫的回答指出并修复了正则表达式的低效率。这里不存在灾难性的回溯。该更改仅稍微延迟了问题,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)
您的正则表达式包含具有类似模式的替代列表,这些模式通常会导致灾难性的回溯并可能影响性能。看看这个模式:
(
(
(\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)
您还可以调整捕获组的数量。