当前位置: 首页 > 知识库问答 >
问题:

为什么Java8拆分有时会删除结果数组开始时的空字符串?

东方智敏
2023-03-14

在Java8之前,当我们在空字符串上拆分时

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"],因此前导空字符串未被删除。

有人能解释一下这是怎么回事吗?在Java8中,split的规则是如何改变的?

共有3个答案

解柏
2023-03-14

split()的文档从Java 7到Java 8略有变化。具体而言,增加了以下声明:

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

(我的重点)

空字符串拆分在开始时生成零宽度匹配,因此根据上面指定的内容,空字符串不包括在结果数组的开始处。相比之下,您的第二个例子在"a"上拆分,在字符串的开始处生成正宽度匹配,因此空字符串实际上包括在结果数组的开始处。

吴均
2023-03-14

这已在拆分(String regex,限制)的留档中指定。

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

"abc"中。拆分(")开头有一个零宽度匹配,因此导致的空子串不包括在结果数组中。

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

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

陆臻
2023-03-14

字符串的行为。split(它调用模式。split)在Java7和Java8之间变化。

比较模式的文档。拆分在Java 7和Java 8中,我们注意到添加了以下条款:

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

同样的子句也被添加到字符串中。与Java 7相比,在Java 8中拆分

让我们比较一下模式的代码。Java 7和Java 8中参考实现的拆分。代码从grepcode中检索,适用于7u40-b43和8-b132版本。

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);
}

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;
            }

若要使拆分在版本间行为一致,并与Java8中的行为兼容:

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

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

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

 类似资料:
  • 问题内容: 在Java 8之前,当我们分割空字符串时 拆分机制将在标有的地方拆分 | 因为”“每个字符前后都有空白。因此,结果将首先生成此数组 稍后将删除结尾的空字符串(因为我们没有为limit参数显式提供负值),因此它将最终返回 在Java 8中,拆分机制似乎已更改。现在当我们使用 我们将得到数组而不是数组,因此看起来开始时的空字符串也被删除了。但是这个理论失败了,因为 在开始时返回带有空字符串

  • 问题内容: 在空字符串上分割将返回大小为1的数组: 考虑这返回空数组: 请解释 :) 问题答案: 出于同样的原因 和 将返回一个大小为2的数组。将第一个匹配之前的所有内容作为第一个元素返回。

  • 问题内容: 我试图使用分隔符拆分值。但是我发现了令人惊讶的结果 我期望得到8个值。[5,6,7,EMPTY,8,9,EMPTY,EMPTY] 但是我只得到6个值。 任何想法以及如何解决。无论EMPTY值在何处,它都应该位于数组中。 问题答案: 默认情况下,从结果数组中删除结尾的空字符串。要关闭此机制,我们需要使用重载版本,将其设置为负值,例如 更多细节: 内部返回该方法的结果,你可以在此方法的文档

  • 任何想法和如何修复。无论空值出现在任何位置,它都应该在数组中。

  • 问题内容: 我想将字符串拆分为单个字符。所以我做: 但这产生了: 在其他语言(例如Ruby)中执行相同操作时,我不习惯使用第一个空字符串。它背后的逻辑是什么? 问题答案: 你为什么要用这个?使用可能会更好。 我知道一个会返回一个字符串数组,而另一个会给您一个字符数组。由于您希望分别使用每个字符,因此我假设这与您的代码无关。

  • 问题内容: path = “/Volumes/Users” >>> path.lstrip(‘/Volume’) ‘s/Users’ >>> path.lstrip(‘/Volumes’) ‘Users’ >>> 我期望的输出是 问题答案: 是基于字符的,它将删除该字符串中左端的所有字符。 要验证这一点,请尝试以下操作: 由于是字符串的一部分,因此将其删除。 您需要改用切片: 或者,在Python