Java-8 带有 `\R` 的正则表达式否定回顾

Java-8 regex negative lookbehind with `\R`

虽然 ,但我编写了一个正则表达式来匹配所有空格,最多包括一个换行符。我对 \R 换行符匹配器使用负后视来做到这一点:

((?<!\R)\s)*

后来我想了想说,哦不,要是有个\r\n呢?它肯定会抓住第一个 linebreakish 字符 \r 然后我会在我的下一个字符串的前面被一个虚假的 \n 卡住,对吧?

所以我回去测试(并可能修复)它。但是,当我测试该模式时,它匹配了整个 \r\n。它不只匹配 \r 离开 \n 正如人们可能期望的那样。

"\r\n".matches("((?<!\R)\s)*"); // true, expected false

但是,当我将 documentation 中提到的 "equivalent" 模式用于 \R 时,它 returns 是错误的。那是 Java 的错误,还是它匹配的正当理由?

构造 \R 是一个 ,它将子表达式包围在一个原子组 (?> parts ) 中。

这就是它不会将它们分开的原因。

注意:如果 Java 接受 lookbehind 中的固定交替,使用 \R 是可以的,但如果引擎不接受,这将引发异常。

实现 #1。文档有误

来源:https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html

这里说:

Linebreak matcher

...is equivalent to \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

但是,当我们尝试使用“等效”模式时,它 returns 错误:

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

所以 Javadoc 应该真的 说:

...is equivalent to (?<!\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])

Oracle 的 Sherman 于 2017 年 3 月 9 日更新 JDK-8176029

"api doc is NOT wrong, the implementation is wrong (which fails to backtracking "0x0d+next.match()" when "0x0d+0x0a + next.match()" fails)"


实现 #2。回顾不只是向后看

尽管名称如此,后视不仅可以向后看,还可以包括甚至跳过当前位置。

考虑以下示例(来自 rexegg.com):

"_12_".replaceAll("(?<=_(?=\d{2}_))\d+", "##"); // _##_

"This is interesting for several reasons. First, we have a lookahead within a lookbehind, and even though we were supposed to look backwards, this lookahead jumps over the current position by matching the two digits and the trailing underscore. That's acrobatic."

对于我们的 \R 示例,这意味着即使我们当前的位置可能是 \n,也不会阻止后视识别它的 \r 后面是\n,然后将两者绑定在一起作为一个原子组,从而拒绝将当前位置后面的\r部分识别为单独的匹配。

注意:为了简单起见,我使用了诸如“我们当前的位置是 \n”之类的术语,但这并不是内部发生的情况的准确表示。