有趣的Java正则表达式限制

时间:2014-07-30 17:45:17

标签: java python regex backtracking

我在Python中尝试过相同的表达式,这似乎没关系,而Java因堆栈溢出而失败。

这是用于演示问题的简化测试用例:

// 10k whitespace.
char[] buf = new char[10000];
Arrays.fill(buf, ' ');
String post = new String(buf);
// All whitespace - works
System.out.println(Pattern.compile(" +").matcher(post).matches());
// All whitespace, or whitespace - Stack Overflow
System.out.println(Pattern.compile("(?: | )+").matcher(post).matches());

第一个正则表达式" +"正常工作。第二个,"( | )+" - 显然也应匹配此字符串,但会导致堆栈溢出。

我想这是一个限制,因为正则表达式(特别是:替代方案)在Java中的实现方式...基于状态机的regexp匹配器似乎没问题(它们将保持在接受状态);或者pythons regexp引擎是否只有更大的堆栈?

通过原子组禁用回溯也有效:"(?> | )+" - 我想在这种情况下,Java将不再在每次匹配时添加6个堆栈帧(显然,堆栈默认情况下不能容纳60000帧)。

这不仅仅是一个理论上的例子。考虑例如"(apple|banana)+"

StringBuilder buf = new StringBuilder();
for (int i = 0; i < 10000; i++)
    buf.append(Math.random() < .5 ? "apple" : "banana");
String s = buf.toString();
System.out.println(s.replaceAll("(apple|banana)+", "lots of fruits"));

使用"(?>apple|banana)+",它会根据需要进行打印lots of fruits;如果没有回溯阻止,它将导致堆栈溢出。

是的,我知道这是灾难式回溯的一种形式 ......让我感到惊讶的是,Java早早失败,其中Python仍然愉快地哼唱,有效地删除了不受欢迎的内容text ...是python更聪明,并认识到这可以处理“贪婪”而无需回溯?或者它只是更好地利用内存?

1 个答案:

答案 0 :(得分:5)

( | )+是可能catastrophic backtracking的一个非常基本的例子。在这种情况下,它可能更好地称为灾难性分支,因为不涉及回溯。

似乎Java可以使用递归实现分支,尽管我不确定10000级深度是否足以触发溢出。也有可能它试图深入2 ^ 10000水平,但由于我对内部结构一无所知,这是纯粹的猜测。 更新:你说1000不足以溢出,所以它绝对看起来是线性的而不是指数级的。

为什么不尝试缩短字符串,并确保在发生溢出之前需要多长时间?另外,尝试类似(apple|banana)+的内容,看看它是否遇到同样的问题。 更新:任何分支方案似乎都会出现此问题。这绝对是Java正则表达式引擎的一个弱点,尽管我无法确切地告诉你原因。除了Python之外,我可以确认它在.NET和JavaScript(我的浏览器,无论如何)中都能正常工作。

我不认为这是NFA驱动引擎中的问题。我假设Java使用另一种方法,但我找不到任何记录的方法。