为什么在Java 8和Java 9中,正则表达式中的\R表现不同?

78

以下代码在Java 8和9中都可以编译,但会有不同的行为。

class Simple {
    static String sample = "\nEn un lugar\r\nde la Mancha\nde cuyo nombre\r\nno quiero acordarme";

    public static void main(String args[]){
        String[] chunks = sample.split("\\R\\R");
        for (String chunk: chunks) {
            System.out.println("Chunk : "+chunk);
        }
    }
}

当我使用Java 8运行它时,它会返回:

Chunk : 
En un lugar
de la Mancha
de cuyo nombre
no quiero acordarme

但是当我在Java 9中运行它时,输出结果不同:

Chunk : 
En un lugar
Chunk : de la Mancha
de cuyo nombre
Chunk : no quiero acordarme
为什么?

4
看起来在Java 8中\R是贪婪的,而在9中则不是。 - user319799
你从 System.getProperty("line.separator") 得到什么字符串? - Sergey Kalinichenko
2
@dasblinkenlight:这不会有影响;\R换行匹配器,它将匹配OP拥有的任何内容。 - Makoto
2
当发布这种问题时,最好包括JDK版本号,因为有时这些是在点发布中修复的错误,然后人们无法复制等。 - Sled
2
@doublep 我不确定你是否会称其为贪婪,但是在匹配\R时,不允许回溯并将单个CR LF序列分成两部分,因为如果有LF跟随,则禁止仅匹配CR。另一种表达方式是它不能回溯。Java 8是正确的;据我所知,Java 9现在与tr18不符。 - tchrist
显示剩余4条评论
2个回答

63

7
对我来说,Java 8的行为看起来更加合理。虽然"\r\n"可以被解释为两个相邻的换行符,但在我看来这没有多大意义。如果你想表示两个换行符,你应该写成"\n\n"或"\r\n\r\n"等,也就是两个相同的换行符。"\r\n"实际上应该只表示一个换行符。 - user319799
2
有道理!但是Java 8具有我所需的行为。嗯。 - Germán Bouzas
3
@GermánBouzas:我猜你需要先规范化换行符,例如使用replaceAll("\\R", "\\n")(没有测试过,但我猜回溯变化在这里不会起任何作用)。 - user319799
9
我非常确定这是一个错误。\R不应该能够回溯; 这有充分的理由。我会看看我能找到什么:您绝不能将CRLF拆分为两个实例或\R - tchrist

49

Java文档与Unicode标准不符。Javadoc错误阐述了\R的匹配方式,它写道:

\R 匹配任何Unicode换行符,等价于 \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]

这份Java文档有缺陷。在《Unicode技术标准#18正则表达式中的R1.6换行符》章节中明确指出:

强烈建议使用一个正则表达式元字符(例如“\R”)来匹配上面列出的所有行尾字符和序列(例如#1中的字符和序列)。这相当于以下表达式的等价形式。该表达式稍微复杂一些,因为需要避免备份。

 (?:\u{D A}|(?!\u{D A})[\u{A}-\u{D}\u{85}\u{2028}\u{2029}]
换句话说,它只能匹配两个码点的CR + LF(回车+换行符)序列,否则只能提供来自该集合的单个码点,前提是它不仅仅是接着一个换行符的回车符。这是因为它不允许后退。对于\ R的正常功能,CRLF必须是原子的。
因此,Java 9不再符合R1.6的强烈建议。此外,它现在正在执行它本应该不执行且在Java 8中未执行的操作。
看起来是时候再次与Sherman(即Xueming Shen)联系了。我曾经与他一起处理过这些正式一致性问题的细节。

2
因此,解决方法是使用(?>\\R)\\R{1}+代替\\R,或者在OP的特定情况下,使用\\R{2}+代替\\R\\R。有趣的是,在Java 9下,即使是\\R{1}\\R{1}\\R{2}也可以得到所需的结果,这是不一致的,因为非占用的{n}不应禁用回溯。 - Holger
也许可以通过JDK-8176983来解决这个问题? - Naman
@nullpointer 有人能告诉我 Java 10 是否已经修复了这个问题吗?看起来 javadoc 仍然有错误的“等效”模式,所以如果不是实现的问题,至少文档是错误的。 - Patrick Parker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接