【问题标题】:Revisited: zero-length matches in Java regex重温:Java 正则表达式中的零长度匹配
【发布时间】:2013-07-30 16:00:23
【问题描述】:

去年我在看这个问题:Zero-length matches in Java Regex:

Pattern pattern = Pattern.compile("a?");
Matcher matcher = pattern.matcher("ababa");
while(matcher.find()){
   System.out.println(matcher.start()+"["+matcher.group()+"]"+matcher.end());
}

产生输出:

0[a]1
1[]1
2[a]3
3[]3
4[a]5
5[]5

我想知道这是否正确。该模式匹配“a”或空字符串。当 Matcher 指向第一个“b”时,没有“a”,所以 find() 匹配空字符串。

但是,javadoc 说:

此方法在此匹配器区域的开头开始,或者,如果 该方法的先前调用成功并且匹配器具有 没有被重置,在第一个不匹配的字符处 上一场比赛。

所以当 Matcher 指向 'b' 时,没有任何字符被匹配并且 find() 匹配一个空字符串,这意味着之后的第一个字符没有被之前的匹配(即空字符串)匹配到是'b'。这应该意味着下一个 find() 应该从同一个地方开始,根据上面的说法,这意味着代码应该无限循环。但当然这不是正在发生的事情。看起来它只是在匹配一个空字符串时将起点提高 1。

那么怎么了?是实现错误,还是 javadoc 遗漏了什么,或者我遗漏了什么?

【问题讨论】:

  • 不,“b”与“a?”不匹配。从输出 1[]1 可以清楚地看出,开始和结束是相同的,所以它是空匹配。但是,在这种情况下 find() 已经消耗了“b”。 ajb 提出的问题是,为什么在这种情况下 find() 会向前发展。答案是匹配组与匹配区域不同(也尝试打印 matcher.regionEnd())。
  • 这仍然没有意义。在它匹配第一个“a”之后,它指向“b”。然后第二个 find() 返回空字符串,根据 JLS,匹配器仍应指向“b”以进行下一个 find(),因为第二个 find() 不匹配“b”。所以在我看来,匹配器应该在第二个 find() 之前处于相同的状态,就像在第三个 find() 之前一样......但是它的某些东西是不同的,因为它返回了不同的结果。 regionStart() 和 regionEnd() 也不会改变(我认为它们只是由 region() 改变)。那么有什么不同呢?

标签: java regex


【解决方案1】:

a? 表示零个或一个“a”字符,因此它将匹配 a 或“无” - 它匹配“a”字符和“无”定位 在“a”和“b”字符之间

这是完全正确和预期的。

【讨论】:

  • 它是正确的,但缺少一个重要的额外细节。默认情况下,正则表达式被配置为贪婪,这意味着它将扩展以匹配可能的最大模式。因此,即使 0[]0 应该在匹配列表中,它也不会被报告,因为 0[a]1 是更大的匹配,在相同的索引处。
【解决方案2】:

好的,当 find() 返回一个空字符串时,看起来确实有一种特殊情况。为了清楚起见,由于我认为有些人不理解这个问题,我的问题是为什么匹配器状态在第二个 find() 和第三个 find() 之前应该不同,因为“第一个字符与前一个字符不匹配match" 在两种情况下都是一样的。

不同的是,上一个匹配的边界存储在匹配器的状态中,它们确实影响下一个find(),但仅在这一种情况下。来自 Matcher.java 中的 find() 代码:

 int nextSearchIndex = last;
 if (nextSearchIndex == first)
      nextSearchIndex++;

last 是搜索开始的地方,除非最后一个 find() 返回一个空字符串(或 firstlast 由其他方法设置),然后它向上移动一个。对此 sn-p 没有评论,所以我不确定其目的是什么,但看起来它是故意制作空字符串匹配的特殊情况。不过,它似乎确实与 javadoc 相矛盾,因为在这种情况下,搜索从 javadoc 所说的位置以外的地方开始。

编辑:顺便说一句,这确实有一个看起来令人惊讶的结果:

 Pattern p = Pattern.compile("a?");
 Matcher m = p.matcher ("abcde");
 m.find();
 System.out.println("[" + m.group() + "]");
 m.find();
 System.out.println("[" + m.group() + "]");
 m.usePattern (Pattern.compile("[bd]"));
 m.find();
 System.out.println("[" + m.group() + "]");

输出

[a]
[]
[d]

最后一个匹配没有找到“b”,即使 'b' 字符没有被任何先前的匹配匹配并且不应该被跳过。不过,这有点晦涩。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-31
    • 1970-01-01
    • 2012-06-29
    • 2017-12-28
    相关资源
    最近更新 更多