Pat*_*ker 6 java regex java-8 regex-lookarounds
在回答另一个问题时,我写了一个正则表达式来匹配所有空格,最多包括一个换行符.我使用负面的lookbehind为\Rlinebreak matcher 做了这个:
((?<!\R)\s)*
Run Code Online (Sandbox Code Playgroud)
之后我正在思考它,我说,哦,不,如果有一个\r\n?当然它会抓住第一个破线的角色\r,然后我会\n在下一个弦的前面陷入虚假,对吗?
所以我回去测试(并且可能修复)它.但是,当我测试模式时,它匹配整个模式\r\n.它与人们可能期望的\r离开不匹配\n.
"\r\n".matches("((?<!\\R)\\s)*"); // true, expected false
Run Code Online (Sandbox Code Playgroud)
然而,当我使用中提到的"等价物"模式文档的\R,它返回false.这是Java的一个错误,还是它有匹配的正当理由?
小智 5
构造\R是一个宏,它将子表达式包围成一个原子组(?> parts ).
这就是为什么它不会将它们分开.
注意:如果Java在lookbehind中接受固定的替换,则使用\Rok,但如果引擎没有,则会抛出异常.
实现#1。文档有误
来源:https ://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
这里说:
换行匹配器
...相当于
\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
然而,当我们尝试使用“等效”模式时,它返回 false:
String _R_ = "\\R";
System.out.println("\r\n".matches("((?<!"+_R_+")\\s)*")); // true
// using "equivalent" pattern
_R_ = "\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]";
System.out.println("\r\n".matches("((?<!"+_R_+")\\s)*")); // false
// now make it atomic, as per sln's answer
_R_ = "(?>"+_R_+")";
System.out.println("\r\n".matches("((?<!"+_R_+")\\s)*")); // true
Run Code Online (Sandbox Code Playgroud)
所以 Javadoc确实应该说:
...相当于
(?<!\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])
Oracle JDK-8176029上的 Sherman 于 2017 年 3 月 9 日更新:
“api 文档没有错误,实现是错误的(当“0x0d+0x0a + next.match()”失败时无法回溯“0x0d+next.match()”)”
实现#2。Lookbehind 不仅仅向后看
尽管有这个名字,lookbehind 不仅能够向后查看,还可以包含甚至跳过当前位置。
考虑以下示例(来自rexegg.com):
"_12_".replaceAll("(?<=_(?=\\d{2}_))\\d+", "##"); // _##_
Run Code Online (Sandbox Code Playgroud)
“这很有趣,有几个原因。首先,我们在后向内有一个前向,尽管我们应该向后看,但这个前向通过匹配两个数字和尾随下划线来跳过当前位置。这太杂技了。”
对于我们的例子来说,这意味着\R即使我们当前的位置可能是\n,但这也不会阻止后向识别它\r后面是\n,然后将两者绑定在一起作为一个原子组,从而拒绝识别\r后面的部分当前位置作为单独的比赛。
注意:为了简单起见,我使用了诸如“我们当前的位置是\n”之类的术语,但这并不是内部发生的情况的准确表示。