以下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”。
感谢任何能解释原因的人!
答案 0 :(得分:5)
正则表达式开头的.*
是贪婪的,所以它最初会尝试匹配尽可能多的字符(整个字符串)。当正则表达式引擎移动到捕获组时,它会发现\ba\w*\b
在字符串末尾无法匹配,但由于该组是可选的,因此它不会回溯并尝试查找匹配项。
要解决此问题,只需将开头的.*
更改为.*?
,它仍会匹配零个或多个字符,但会尝试尽可能少地匹配(懒惰而不是贪婪):
Pattern p = Pattern.compile("^.*?(\\ba\\w*\\b)?.*$");
另一种选择是通过删除后面的?
来使您的捕获组成为必需。这会强制正则表达式引擎回溯,直到进行组匹配。这可能不是你想要的,因为它会改变正则表达式的含义(匹配的字符串会更少)。
编辑:看起来我真的应该测试一下这个!事实证明只是将.*
更改为.*?
在这里没有用,因为您的群组在开头仍然无法匹配,整个字符串将与.*
匹配最后(即使你把它改为.*?
)。
这里最好的选择是删除组后的?
,以便需要该组。如果您仍想匹配所有字符串,但对于与您的组不匹配的字符串,该组为null,则可以使用以下正则表达式:
^(?:.*(\ba\w*\b).*|.*)$
答案 1 :(得分:1)
F.J。关于原因是正确的。
要在行上显式匹配以a
开头的第一个word-char序列,您可以匹配任何数量的非单词字符或以a
以外的ASCII字母开头的单词,然后是一个可选的捕获的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
,请将if
替换为$
。
最后,$
并不意味着java中的输入结束。它表示输入结束或输入结束时的换行符之前。 javadoc解释了端锚之间的细微差别:
\Z
行的结尾
\z
输入的结束,但是对于最终的终结符,如果有的话 {{1}}输入的结尾