【问题标题】:Why does Java regex engine throw StringIndexOutOfBoundsException on a + repetition?为什么 Java 正则表达式引擎在 + 重复时抛出 StringIndexOutOfBoundsException?
【发布时间】:2011-04-11 12:51:24
【问题描述】:

我已经编写了一个正则表达式模式来查找斐波那契数(不管为什么,我只是这样做了)。它按预期工作得非常好 (see on ideone.com):

    String FIBONACCI = 
        "(?x) .{0,2} | (?: (?=(\\2?)) (?=(\\2\\3|^.)) (?=(\\1)) \\2)++ . ";

    for (int n = 0; n < 1000; n++) {
        String s = new String(new char[n]);
        if (s.matches(FIBONACCI)) {
            System.out.print(n + " ");
        }
    } // 0 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 

possessive 重复(即主“循环”上的 ++)至关重要,因为您不希望使用此匹配算法进行回溯。但是,使重复可回溯(即仅在主“循环”上使用+)不会导致不匹配,而是会导致运行时异常!!! (as seen on ideone.com):

Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
    String index out of range: -1

    at java.lang.String.charAt(String.java:686)
    at java.lang.Character.codePointAt(Character.java:2335)
    at java.util.regex.Pattern$CharProperty.match(Pattern.java:3344)
    at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:3994)
    at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:3966)
    at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3916)
    at java.util.regex.Pattern$Branch.match(Pattern.java:4114)
    at java.util.regex.Matcher.match(Matcher.java:1127)
    at java.util.regex.Matcher.matches(Matcher.java:502)
    at java.util.regex.Pattern.matches(Pattern.java:930)
    at java.lang.String.matches(String.java:2090)

有人能解释一下这里发生了什么吗?这是 Java 正则表达式引擎中的错误吗?

【问题讨论】:

  • 我敢打赌这是因为正则表达式引擎已经厌倦了被要求做一些愚蠢的事情:-)。
  • regex 查找斐波那契数?聚,你疯了!
  • 现在这是一个混乱的常规异常...... :-) 仍然试图让我的头脑围绕你的正则表达式......
  • 看起来像一个错误。如果有帮助,它似乎通过了 4 并在 5 上失败(应该匹配) - ideone.com/EXStT 。我最近也很无聊,做了一些与balancing group相关的事情,但这并不难:)
  • 对于它的价值,即使是 .NET 中的可回溯重复也有效 (ideone.com/yB16g)。我认为回溯肯定会导致不匹配。我想即使我不完全理解我自己的算法 =)。

标签: java regex fibonacci nested-reference


【解决方案1】:

错误 ID 6984178

有许多与引擎抛出 StringIndexOutOfBoundsException (see: search results) 相关的错误。特别是这个错误已被报告并在内部被接受为 Bug ID 6984178(这可能需要一段时间才能显示在外部数据库中) .

这里有一个更简单的模式来重现错误 (see also on ideone.com):

System.out.println(
   "abaab".matches("(?x) (?: (?=(a+)) \\1 b )* x")
); // StringIndexOutOfBounds: -1

请注意,使用 *?*+ 只会按预期返回 false

看起来问题是由在前瞻内引用捕获组时尝试回溯贪婪重复触发的:越界索引是第一个和第二个之间的长度差a+ (例如"aabaaaaab" 得到-3)。

可能必须调试java.util.regex.Pattern source code 才能查明错误的确切性质。


探索斐波那契模式

在 Java 引擎上,贪婪回溯+

这里有一个更详细的 sn-p 来展示引擎在这个模式上的表现:

String FIBONACCI = 
    "(?x) .{0,2} | (?: (?=(\\2|^)) (?=(\\2\\3|^.)) (?=(\\1)) \\2)+ . ";

for (int n = 0; n < 1000; n++) {
    String s = new String(new char[n]);
    try {
        if (s.matches(FIBONACCI)) {
            System.out.printf("%n%s", n);
        }
    } catch (StringIndexOutOfBoundsException e) {
        String index = e.getMessage().replace("String index out of range: ", "");
        System.out.printf(" <%s:%s>", n, index);
    }
}

(稍微编辑的)输出是(as seen on ideone.com):

0 1 2 3 <5:-1>
6 <7:-1> ... <12:-1> <13:-3>
14 <15:-3> ... <33:-3> <34:-8>
35 <36:-8> ... <88:-8> <89:-21>
90 <91:-21> ... <232:-21> <233:-55>
234 <235:-55> ... <609:-55> <610:-144>
611 <612:-144> ...

因此引擎以某种方式尝试访问 -1、-3、-8、-21、-55、-144 等处的字符串索引。请注意,这些是每隔一个斐波那契数,但为负数。另请注意,除了前几个数字之外,其余的匹配项(6、14、35,...)不是斐波那契数。


.NET 引擎,贪婪回溯+

虽然该模式最初是在考虑所有格量词的必要性的情况下编写的,但实际上回溯重复仍然会产生正确的答案(假设引擎不像 Java 那样有错误)。这是 .NET 引擎 (see also on ideone.com) 上的 C# 实现:

Regex r = new Regex(
  @"(?x) ^.{0,1}$ | ^(?: (?=(\2?)) (?=(\2\3|^.)) (?=(\1)) \2)+ . $ "
);

for (int n = 0; n < 1000; n++) {
  if (r.IsMatch("".PadLeft(n))) {
    Console.Write("{0} ", n);
  }
}
// 0 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 

如您所见,输出是正确的,即使是回溯+“循环”。事实上,正是因为它是一个回溯循环,特殊情况可以限制为 .{0,1} 而不是 .{0,2}


在 Java 引擎上,不情愿地回溯+?

这在 Java 中按预期工作。另外,因为不情愿,我们也可以将特殊情况限制为 .{0,1} (see also on ideone.com):

String FIBONACCI = 
        "(?x) .{0,1} | (?: (?=(\\2|^)) (?=(\\2\\3|^.)) (?=(\\1)) \\2)+? . ";

关于算法

公式

该模式利用Second Identity of Fibonacci Numbers:

这可以通过归纳来证明。

模式

让我们使用这个版本的模式(它适用于 Java,并且在锚定时也适用于 C#):

                                                     summation 
free-space!                                            "loop"
    ↓                                                     ↓
   (?x) .{0,1} | (?: (?=(\2|^)) (?=(\2\3|^.)) (?=(\1)) \2)+? .
        \____/   \___________________________________/  ↑    ↑  
      base case      inductive case                    +Fi  +1
     for n = 0,1
                     (assertions don't "count" toward sum)!
                        $1 := $2    (or initialized with 0)
                        $2 := $2+$3 (or initialized with 1)
                        $3 := $1    (a temporary variable for the "swap")

斐波那契数列的生成很简单,基于[$1, $2] := [$2, $2+$1] 转换。由于断言是按顺序执行的,因此引入了一个临时变量(就像单赋值“伪代码”版本一样)。

【讨论】:

  • 仅供参考,这些错误报告中的大多数都涉及格式错误的正则表达式或替换字符串以及它们的处理方式。唯一的错误是它应该抛出 PatternSyntaxException (regex) 或 IllegalArgumentException (replacement),而不是 SIOOBE。我试图让他们改变这一点,但无济于事。
猜你喜欢
  • 2010-09-22
  • 2013-04-07
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
  • 1970-01-01
  • 2016-05-21
  • 1970-01-01
相关资源
最近更新 更多