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

在Java 8之前,当我们分割空字符串时

String[] tokens = "abc".split("");

拆分机制将在标有的地方拆分 |

|a|b|c|

因为”“每个字符前后都有空白。因此,结果将首先生成此数组

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

稍后将删除结尾的空字符串(因为我们没有为limit参数显式提供负值),因此它将最终返回

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

在Java 8中,拆分机制似乎已更改。现在当我们使用

"abc".split("")

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

"abc".split("a")

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

有人可以解释一下这是怎么回事,以及拆分规则在Java 8中是如何变化的?

回答:

String.split(调用Pattern.split)的行为在Java 7和Java 8之间改变。

Pattern.split在Java 7和Java 8的文档之间进行比较,我们观察到添加了以下子句:

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

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

让我们比较一下Pattern.split 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;

}

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

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

    (?!\A) 检查字符串是否不以字符串开头结尾,这意味着匹配在字符串开头为空匹配。

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

以上是 为什么在Java 8中,split有时会删除结果数组开头的空字符串? 的全部内容, 来源链接: utcz.com/qa/428035.html

回到顶部