为什么在Java 8 split中有时会在结果数组的开头删除空字符串?

Psh*_*emo 107 java regex split java-8

在Java 8之前我们拆分空字符串之类的

String[] tokens = "abc".split("");
Run Code Online (Sandbox Code Playgroud)

分裂机制会在标有的地方分开 |

|a|b|c|
Run Code Online (Sandbox Code Playgroud)

因为""每个字符前后都有空格.因此,它最初将生成此数组

["", "a", "b", "c", ""]
Run Code Online (Sandbox Code Playgroud)

然后将删除尾随的空字符串(因为我们没有明确地为limit参数提供负值),所以它最终会返回

["", "a", "b", "c"]
Run Code Online (Sandbox Code Playgroud)

在Java 8中,拆分机制似乎已经发生了变化.现在我们用的时候

"abc".split("")
Run Code Online (Sandbox Code Playgroud)

我们将得到["a", "b", "c"]数组,而不是["", "a", "b", "c"]看起来像开始时的空字符串也被删除.但是这个理论失败了,例如

"abc".split("a")
Run Code Online (Sandbox Code Playgroud)

在start时返回带有空字符串的数组["", "bc"].

有人可以解释这里发生了什么,以及这些案例的拆分规则在Java 8中是如何变化的?

nha*_*tdh 83

Java 7和Java 8之间String.split(调用Pattern.split)的行为发生了变化.

文档

的文档之间的比较Pattern.split的Java 7Java的8,我们遵守以下条款添加:

当在输入序列的开头存在正宽度匹配时,在结果数组的开头包括空的前导子串.然而,开头的零宽度匹配从不会产生这样的空前导子串.

Java 7相比String.split,Java 8中还添加了相同的子句.

参考实施

让我们比较Pattern.splitJava 7和Java 8中的参考实现的代码.从grepcode中检索代码,版本为7u40-b43和8-b132.

Java 7

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}
Run Code Online (Sandbox Code Playgroud)

Java 8

public String[] split(CharSequence input, int limit) {
    int index = 0;
    boolean matchLimited = limit > 0;
    ArrayList<String> matchList = new ArrayList<>();
    Matcher m = matcher(input);

    // Add segments before each match found
    while(m.find()) {
        if (!matchLimited || matchList.size() < limit - 1) {
            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }
            String match = input.subSequence(index, m.start()).toString();
            matchList.add(match);
            index = m.end();
        } else if (matchList.size() == limit - 1) { // last one
            String match = input.subSequence(index,
                                             input.length()).toString();
            matchList.add(match);
            index = m.end();
        }
    }

    // If no match was found, return this
    if (index == 0)
        return new String[] {input.toString()};

    // Add remaining segment
    if (!matchLimited || matchList.size() < limit)
        matchList.add(input.subSequence(index, input.length()).toString());

    // Construct result
    int resultSize = matchList.size();
    if (limit == 0)
        while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
            resultSize--;
    String[] result = new String[resultSize];
    return matchList.subList(0, resultSize).toArray(result);
}
Run Code Online (Sandbox Code Playgroud)

在Java 8中添加以下代码排除了输入字符串开头的零长度匹配,这解释了上面的行为.

            if (index == 0 && index == m.start() && m.start() == m.end()) {
                // no empty leading substring included for zero-width match
                // at the beginning of the input char sequence.
                continue;
            }
Run Code Online (Sandbox Code Playgroud)

保持兼容性

遵循Java 8及更高版本中的以下行为

要使split各个版本的行为保持一致并与Java 8中的行为兼容:

  1. 如果你的正则表达式可以匹配零长度字符串,只需(?!\A)在正则表达式的末尾添加并将原始正则表达式包装在非捕获组中(?:...)(如果需要).
  2. 如果你的正则表达式不能匹配零长度字符串,你不需要做任何事情.
  3. 如果您不知道正则表达式是否可以匹配零长度字符串,请执行步骤1中的两个操作.

(?!\A) 检查字符串是否不在字符串的开头结束,这意味着匹配是字符串开头的空匹配.

遵循Java 7及之前的行为

没有通用的解决方案可以split向后兼容Java 7和之前的版本,split只需将所有实例替换为指向您自己的自定义实现.

  • @Daniel:通过在正则表达式的末尾添加`(?!^)`并将原始正则表达式包装在非捕获组中,可以使其向前兼容(遵循Java 8的行为)。 (:...)`(如有必要),但我想不出任何办法使其向后兼容(遵循Java 7和以前的旧行为)。 (2认同)

Ale*_* C. 30

这已在文档中指定split(String regex, limit).

当在该字符串的开头存在正宽度匹配时,在结果数组的开头包含空的前导子字符串.然而,开头的零宽度匹配从不会产生这样的空前导子串.

"abc".split("")开头有一个零宽度匹配,因此前导空子字符串不包含在结果数组中.

但是,在拆分时,在第二个片段中,"a"您获得了正宽度匹配(在本例中为1),因此按预期包含空的前导子字符串.

(删除了不相关的源代码)

  • @PaulVargas"开源"中的"开放"确实代表某种东西. (12认同)
  • @PaulVargas公平地说我不知道​​,但我认为它可以,因为你可以下载JDK,并解压缩包含所有源的src文件.所以从技术上讲,每个人都可以看到来源. (6认同)
  • 这只是一个问题.可以从JDK发布代码片段吗?还记得Google的版权问题 - 哈利波特 - 甲骨文吗? (3认同)
  • @ZouZou:只是因为每个人都能看到它并不意味着你可以重新发布它 (2认同)
  • @Paul Vargas,IANAL但在很多其他场合这种类型的帖子属于报价/合理使用情况.有关该主题的更多信息,请访问:http://meta.stackexchange.com/questions/12527/do-i-have-to-worry-about-copyright-issues-for-code-posted-on-stack-overflow (2认同)
  • 这引用了代码的错误部分.您只引用了Java 8 String.split的快速轨道,其中如果分隔符包含1个非正则表达式字符,则与其余字符分开处理. (2认同)

ars*_*jii 14

split()从Java 7到Java 8 的文档略有变化.具体来说,添加了以下语句:

当在该字符串的开头存在正宽度匹配时,在结果数组的开头包含空的前导子字符串.然而,开头的零宽度匹配从不会产生这样的空前导子串.

(强调我的)

空字符串拆分在开头生成零宽度匹配,因此根据上面指定的内容,在结果数组的开头不包括空字符串.相比之下,分裂的第二个示例在字符串的开头"a"生成宽度匹配,因此实际上在结果数组的开头包含空字符串.

  • 尽管新行为看起来更符合*逻辑*,但它显然是**向后兼容性突破**.这种改变的唯一理由是"some-string".split("")`是一种非常罕见的情况. (5认同)
  • `.split("")`不是没有匹配任何东西的唯一分裂方式.我们使用了一个积极的前瞻性正则表达式,它在jdk7中也在开头匹配并产生了一个现在已经消失的空头元素.https://github.com/spray/spray/commit/5ab4fdf9ebd8986297e0137bc07088c6223276a0 (4认同)
  • @PaulVargas实际上在这里arshajii在ZouZou前几秒发布了答案,但遗憾的是ZouZou早些时候回答了我的问题[这里](http://stackoverflow.com/questions/22718096/why-a-in-the-0th-index-of-an -array-ON-perfoaming-A-分裂WO-定界符#comment34621049_22718222).我想知道我是否应该问这个问题,因为我已经知道了答案,但这看起来很有意思,并且ZouZou因其先前的评论而应该得到一些声誉. (2认同)