模式匹配导致长输入上的Stackoverflow

时间:2014-01-11 09:55:47

标签: java regex stack-overflow

我知道有关于此主题的几个主题,但找到的解决方案特定于特定问题,并且基于改进正则表达式字符串。

无论如何,我需要处理一个文本输入文件,其中包含的数据将图形结构建模为相邻列表。每行 n 包含与 n 相邻的顶点列表(每个都标记为整数),由一个或多个空格字符分隔。在解析之前,我决定使用正则表达式字符串检查每一行,而不是在数据不良的情况下抛出并捕获NumberFormatExceptions

所以这就是代码在这一点上所喜欢的:

line = line.trim(); //Remove whitespace in the beginning and in the end of line
if (Pattern.matches("(\\d+\\s*)*", line)){
    //split string and parse vertices
}

一行也可能为空,这意味着它的度数为0.

它适用于大多数实例,但在解析具有~1000个相邻顶点的线时失败(StackOverflow)。我想知道我可以解决这个问题的各种方法。我不想增加JVM堆栈大小,因为程序应保持可移植性。此外,我想继续使用RegEx模式匹配,因为嘿,这就是它的用途!所以希望有人有个好主意。

3 个答案:

答案 0 :(得分:5)

已提供替代解决方案,因此我不会涉及这些问题。我将解释为什么你的模式导致StackOverflowError 1 ,以及你的正则表达式如何也会受到灾难性的回溯。

如果您的JVM使用OpenJDK Java类库,则会出现

1 StackOverflowError。对于其类库使用其他内容的JVM可能不会发生。但是,由于Oracle的JRE(最常见的JRE)使用OpenJDK Java类库(Oracle实际上维护OpenJDK),因此在编写正则表达式时必须考虑此错误。在相关的说明中,过去曾多次报告这是一个错误 - here is one of them,并且仍然没有修复。

不接受这个答案,因为它无法解决问题

的StackOverflowError

为了匹配量词重复的子模式(占有量词除外),Pattern可以递归调用内部函数进行匹配,因此每次迭代使用一些堆栈

它通常会对子模式进行一些分析,以避免对\w+(?:df)+之类的简单模式进行递归调用,但是它被迫对(?:gd?f)*或{{进行递归调用1}}。注意前一种情况没有选择点,但后一种情况有选择点。具有选择点的常见模式是量词(占有率排除)或替代(?:g|d|f)

如果输入字符串足够长,以便子模式重复数千次,您的模式将获得|,因为它包含选择点。

占有量词没有选择点,因为它不允许回溯。引擎无需回溯,因此无需递归调用以在堆栈上存储信息。

灾难性的回溯

您的模式会受到灾难性的回溯,因为您可以在不同的迭代次数中找到可由您的正则表达式匹配的字符串。在匹配输入中,通常会探索一个或两个分支,但是当输入字符串是失败输入时,将探索所有这些分支。

例如,让我们使用带有正则表达式StackOverflowError的简单输入1234。它可以通过上面的正则表达式以几种不同的方式匹配。

(\\d+\\s*)*

1/2/3/4 (4 iterations) 1/2/34 (3 iterations) 12/34 (2 iterations) 1234 (1 iteration) 1/23/4 (3 iterations) 之类的输入失败时,引擎将回溯您分割号码的所有方式。下面的曲目显示了引擎尝试的前几次尝试:

1234 5678 x

如果输入包含一个很长的数字,以及许多这样的数字,那么你将会进入一个回溯的地狱。

答案 1 :(得分:1)

您没有使用正则表达式来提取单个数字,因此您只需检查以确保该行仅包含数字和空格即可。您的正则表达式可以像"[\\d\\s]*"一样简单。

答案 2 :(得分:1)

您想要使用

^[\\d\\s]+$

基本上,这将匹配空白和数字的序列,确保在行的开头和结尾之间没有其他字符。你的原始版本在3000多个步骤中解析我的测试字符串,这需要3个步骤。

这有以下结果:

123123  123123  123123  123123 - OK
1 - OK
123g 12r3123 123 - NOT OK