1

以下 Java 代码旨在捕获单词“abc”,但它给出了“null”:

Pattern p = Pattern.compile("^.*(\\ba\\w*\\b)?.*$");
Matcher m = p.matcher("xxx abc yyy");
if (m.matches()) System.out.println(m.group(1));

如果删除问号,它会正确捕获“abc”。问号是贪心的,所以我原以为原始代码也应该给出“abc”。

感谢任何可以解释原因的人!

4

2 回答 2

5

.*则表达式开头的 是贪婪的,因此它最初会尝试匹配尽可能多的字符(整个字符串)。当正则表达式引擎移动到捕获组时,它会在字符串的末尾看到\ba\w*\b无法匹配,但由于该组是可选的,它不会回溯并尝试找到匹配项。

要解决此问题,只需.*将开头的 更改为.*?,它仍将匹配零个或多个字符,但它会尝试尽可能少地匹配(懒惰而不是贪婪):

Pattern p = Pattern.compile("^.*?(\\ba\\w*\\b)?.*$");

另一种选择是通过删除?after 来使您的捕获组成为必需的。这将迫使正则表达式引擎回溯,直到进行组匹配。但这可能不是您想要的,因为它会改变正则表达式的含义(匹配的字符串更少)。

编辑:看起来我真的应该测试一下!事实证明,仅更改.*to.*?在这里没有帮助,因为您的组仍然无法在开头匹配,并且整个字符串将.*在结尾处匹配(即使您将其更改为.*?)。

您最好的选择是?在组之后删除该组,以便需要该组。如果您仍想匹配所有字符串,但对于与您的组不匹配的字符串,组为空,您可以使用以下正则表达式:

^(?:.*(\ba\w*\b).*|.*)$
于 2012-11-07T17:17:03.260 回答
1

FJ关于原因是正确的。


要显式匹配以一行开头的第一个 word-char 序列a,您可以匹配任意数量的非单词字符或以 ASCII 字母开头的单词,而不是a,然后是可选的捕获a单词,后面可能跟被忽略的内容。

该程序abc按预期打印

import java.util.regex.*;

public class Foo {
  public static void main(String[] argv) {
    Pattern p = Pattern.compile("^(?:\\W|[b-zA-Z]\\w+)*(?:(a\\w*)?(?:.*))$");
    Matcher m = p.matcher("xxx abc yyy");
    if (m.matches()) System.out.println(m.group(1));
  }
}

正则表达式是明确的,因此只需要对字符串进行一次前向传递。不过,它确实需要更仔细的阅读。

我的倾向是这些情况通常是显式标记化——分成单词和非单词,然后循环遍历数组以查找您想要的内容。


或者,您可以使用未锚定的正则表达式find来代替。match

find()尝试查找与模式匹配的输入序列的下一个子序列。

所以你可以做

Pattern p = Pattern.compile("(\\ba\\w*\\b)?");
Matcher m = p.matcher("xxx abc yyy")
while (m.find()) { System.out.println(m.group(1)); }

如果你只想要第一个,或者while用 an替换。if


最后,$并不意味着 java 中输入的结束。这意味着输入结束或输入结束时的换行符之前。javadoc 解释了端锚之间的细微差别:

$ 一行
\Z 的结尾 输入的结尾,但对于最后的终止符,如果有的话
\z 输入的结尾

于 2012-11-07T17:19:25.670 回答