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

为什么在Java8中,split有时会删除结果数组开头的空字符串?,java,regex,split,java-8,Java,Regex,Split,Java 8,在Java 8之前当我们在空字符串上拆分时,如 String[] tokens = "abc".split(""); 拆分机制将在标有| |a|b|c| 因为每个字符前后都有空格“”。所以它首先会生成这个数组 ["", "a", "b", "c", ""] 稍后会(因为我们没有显式地为limit参数提供负值),所以它最终会返回 [""

在Java 8之前当我们在空字符串上拆分时,如

String[] tokens = "abc".split("");
拆分机制将在标有
|

|a|b|c|
因为每个字符前后都有空格
”。所以它首先会生成这个数组

["", "a", "b", "c", ""]
稍后会(因为我们没有显式地为
limit
参数提供负值),所以它最终会返回

["", "a", "b", "c"]

在Java8中拆分机制似乎已经改变。现在当我们使用

"abc".split("")
我们将获得
[“a”、“b”、“c”]
数组,而不是
[”、“a”、“b”、“c”]

我的第一个猜测是,可能现在前导的空字符串也会像尾随的空字符串一样被删除

但是这个理论失败了,因为

"abc".split("a")
返回
[“”,“bc”]
,因此未删除前导空字符串


有人能解释一下这是怎么回事吗?Java 8中的
split
规则是如何变化的?

从Java 7到Java 8的文档中有一点变化。具体而言,增加了以下声明:

如果此字符串的开头有正宽度匹配,则结果数组的开头将包含一个空的前导子字符串开始时的零宽度匹配不会产生这样的空前导子字符串。

(强调矿山)


空字符串拆分在开始处生成零宽度匹配,因此根据上面的指定,空字符串不包括在结果数组的开始处。相比之下,第二个示例在
“a”
上拆分,该示例在字符串的开头生成正宽度匹配,因此实际上在生成的数组的开头包含一个空字符串。

的文档中已经指定了这一点

当此字符串开头有正宽度匹配时 然后一个空的前导子字符串包含在 结果数组。开始时的零宽度匹配,但从不 生成这样的空前导子字符串

“abc”.split(“”
中,您在开始时得到了零宽度匹配,因此前导的空子字符串不包括在结果数组中

但是,在第二个代码段中,当您在
“a”
上拆分时,您得到了一个正宽度匹配(在本例中为1),因此空的前导子字符串按预期包含在内


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

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

文档 比较和中
模式.split
的文档,我们注意到添加了以下条款:

如果在输入序列的开头有正宽度匹配,则在结果数组的开头包含一个空的前导子字符串。但是,开始时的零宽度匹配永远不会产生这样的空前导子字符串

中的
String.split
in中也添加了相同的子句,而不是

参考实现 让我们比较一下Java7和Java8中参考实现的
Pattern.split
代码。对于版本7u40-b43和8-b132,该代码从grepcode中检索

爪哇7 保持兼容性 Java8及更高版本中的以下行为 要使
split
在不同版本中的行为一致,并与Java 8中的行为兼容,请执行以下操作:

  • 如果您的正则表达式可以匹配零长度字符串,只需在正则表达式的末尾添加
    (?!\A)
    ,并将原始正则表达式包装到非捕获组
    (?:…)
    (如有必要)
  • 如果正则表达式无法匹配零长度字符串,则无需执行任何操作
  • 如果您不知道正则表达式是否可以匹配零长度字符串,请执行步骤1中的两个操作
  • (?!\A)
    检查字符串是否不在字符串开头结束,这意味着匹配在字符串开头是空匹配

    Java7和更早版本中的以下行为
    除了替换
    split
    的所有实例以指向您自己的自定义实现之外,没有通用的解决方案可以使
    split
    向后兼容Java 7和更早版本。

    还需要几秒钟的时间​​不同之处。@PaulVargas实际上在这里arshajii在Zouzu之前几秒钟发布了答案,但不幸的是,Zouzu早些时候回答了我的问题。我想知道我是否应该问这个问题,因为我已经知道了答案,但这似乎很有趣,邹祖之前的评论也值得一提。尽管新的行为看起来更符合逻辑,但这显然是向后兼容的中断。此更改的唯一理由是
    “某些字符串”。拆分(“”
    是一种非常罕见的情况。
    。拆分(“”
    不是唯一不匹配任何内容的拆分方法。我们在jdk7中使用了一个积极的前瞻正则表达式,它在开始时也匹配了,并生成了一个空的head元素,现在已经不存在了。这只是个问题。可以从JDK发布一段代码吗?还记得Google-Harry Potter-Oracle的版权问题吗?@PaulVargas公平地说,我不知道,但我认为这没问题,因为你可以下载JDK,并解压缩包含所有源代码的src文件。所以从技术上讲,每个人都可以看到源代码。@PaulVargas“开源”中的“开放”确实代表了一些东西。@Zouzu:仅仅因为每个人都可以看到它并不意味着你可以重新发布it@PaulVargas,IANAL,但在许多其他情况下,此类职位属于报价/合理使用情况。关于这个主题的更多信息在这里:Java8似乎解决了这个问题。同时,
    s.split((?!^)”)
    似乎有效。@我问题中描述的shkschneider行为不是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) {
                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);
    }
    
    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);
    }
    
                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;
                }