以下 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”。
感谢任何可以解释原因的人!
正.*
则表达式开头的 是贪婪的,因此它最初会尝试匹配尽可能多的字符(整个字符串)。当正则表达式引擎移动到捕获组时,它会在字符串的末尾看到\ba\w*\b
无法匹配,但由于该组是可选的,它不会回溯并尝试找到匹配项。
要解决此问题,只需.*
将开头的 更改为.*?
,它仍将匹配零个或多个字符,但它会尝试尽可能少地匹配(懒惰而不是贪婪):
Pattern p = Pattern.compile("^.*?(\\ba\\w*\\b)?.*$");
另一种选择是通过删除?
after 来使您的捕获组成为必需的。这将迫使正则表达式引擎回溯,直到进行组匹配。但这可能不是您想要的,因为它会改变正则表达式的含义(匹配的字符串更少)。
编辑:看起来我真的应该测试一下!事实证明,仅更改.*
to.*?
在这里没有帮助,因为您的组仍然无法在开头匹配,并且整个字符串将.*
在结尾处匹配(即使您将其更改为.*?
)。
您最好的选择是?
在组之后删除该组,以便需要该组。如果您仍想匹配所有字符串,但对于与您的组不匹配的字符串,组为空,您可以使用以下正则表达式:
^(?:.*(\ba\w*\b).*|.*)$
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
输入的结尾