使用Regex解析大字符串以获取java.lang.StackOverflowError错误

时间:2016-09-07 05:21:45

标签: java regex stack-overflow tokenize

这是我的正则表达式

"(?<=\"body\":\")((?=\",|\"$)|.)+"

它标记了正文上的字符串。例如,

"body":its my string

结果是

its my string

但是当我使用大输入字符串时,我收到此错误

Exception in thread "main" java.lang.StackOverflowError
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4168)
at java.util.regex.Pattern$Loop.match(Pattern.java:4295)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4227)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4078)
at java.util.regex.Pattern$CharProperty.match(Pattern.java:3345)
at java.util.regex.Pattern$Branch.match(Pattern.java:4114)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4168)
at java.util.regex.Pattern$Loop.match(Pattern.java:4295)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4227)

这是如何引起的?如何解决?

1 个答案:

答案 0 :(得分:2)

您似乎正在尝试捕获RHS字符串的内容,同时强制它先于"body":",然后是"

您似乎使用外观断言来测试周围文本的存在,但您还使用捕获组来捕获RHS字符串的内容。你不需要做这两件事。 Lookaround断言是零宽度,这意味着它们不会形成最终匹配子字符串的一部分。最终匹配的子字符串始终可以作为捕获组0访问。或者,您可以完全匹配所有正则表达式组件(意味着非零宽度匹配,意味着没有外观)并使用捕获组来提取感兴趣的子字符串,但这将是效率低下。

以下是我认为应该如何写这个(与此演示的args[0]匹配):

Pattern p = Pattern.compile("(?<=\"body\":\")[^\"]*(?=\")");
Matcher m = p.matcher(args[0]);
if (!m.find(0)) { System.out.println("doesn't match!"); System.exit(1); }
System.out.println(m.group(0));

上面的内容适用于相当大的字符串。

我确实尝试重现StackOverflowError例外,但我成功了。在我看来,Java正则表达式引擎正在使用递归来实现重复交替的匹配。这对我来说非常令人惊讶,因为我不知道为什么根本需要进行递归来匹配重复的替换。话虽这么说,我也用Perl正则表达式进行了一些测试,我一直认为它是现存中最强大和最强大的正则表达式,并且让我更加惊讶地发现Perl以完全相同的方式失败作为Java正则表达式。

下面是一个示例,显示Java失败和Perl失败。对于此演示,我将[^"]原子更改为交替(?:\\.|[^"]),这有效地增加了对双引号字符串中嵌入的反斜杠转义码的支持,例如\"以编码嵌入式双 - 引用,这在许多编程环境中都得到了普遍支持。

<强>爪哇

Pattern p = Pattern.compile("(?<=\"body\":\")(?:\\\\.|[^\"])*(?=\")");
Matcher m = p.matcher(args[0]);
if (!m.find(0)) { System.out.println("doesn't match!"); System.exit(1); }
System.out.println(m.group(0));

输出

Exception in thread "main" java.lang.StackOverflowError
  at java.util.regex.Pattern$CharProperty.match(Unknown Source)
  at java.util.regex.Pattern$Branch.match(Unknown Source)
  at java.util.regex.Pattern$GroupHead.match(Unknown Source)
  at java.util.regex.Pattern$Loop.match(Unknown Source)
  at java.util.regex.Pattern$GroupTail.match(Unknown Source)
  at java.util.regex.Pattern$BranchConn.match(Unknown Source)
  at java.util.regex.Pattern$CharProperty.match(Unknown Source)
  at java.util.regex.Pattern$Branch.match(Unknown Source)
  at java.util.regex.Pattern$GroupHead.match(Unknown Source)
  at java.util.regex.Pattern$Loop.match(Unknown Source)
  at java.util.regex.Pattern$GroupTail.match(Unknown Source)
...

Perl(来自shell)

largeString="\"body\":\"$(perl -e 'use strict; use warnings; for (my $i = 0; $i < 2**15; ++$i) { print("x"); }';)\"";
perl -e 'use strict; use warnings; my $re = qr((?<="body":")(?:\\.|[^"])*(?=")); if ($ARGV[0] !~ $re) { print("didn'\''t match!\n"); } print($&);' "$largeString";

输出

Complex regular subexpression recursion limit (32766) exceeded at -e line 1.
didn't match!
Use of uninitialized value $& in print at -e line 1.

所以,只是为了澄清,我的解决方案在我的答案开始附近给出的原因避免了这个堆栈溢出错误并不是因为我删除了捕获组1,而是因为我删除了交替。同样,我不知道为什么用递归实现重复的替换,但鉴于这一事实,大输入字符串会导致堆栈溢出错误似乎是合乎逻辑的。