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

标签 java regex split java-8

在 Java 8 之前 当我们像这样拆分空字符串时

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

拆分机制会在标记为|的地方拆分

|a|b|c|

因为空格 "" 存在于每个字符前后。因此它首先会生成这个数组

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

以后会 remove trailing empty strings (因为我们没有明确地为 limit 参数提供负值)所以它最终会返回

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

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

"abc".split("")

我们将得到 ["a", "b", "c"] 数组而不是 ["", "a", "b", "c"].

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

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

"abc".split("a")

返回 ["", "bc"],因此未删除前导空字符串。

有人能解释一下这是怎么回事吗? split 的规则在 Java 8 中发生了哪些变化?

最佳答案

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

文档

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

When there is a positive-width match at the beginning of the input sequence then an empty leading substring is included at the beginning of the resulting array. A zero-width match at the beginning however never produces such empty leading substring.

相同的子句也添加到 Java 8 中的 String.split , 与 Java 7 相比.

引用实现

让我们比较 Java 7 和 Java 8 中引用实现的 Pattern.split 代码。代码是从 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);
}

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

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

保持兼容性

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

要使 split 在不同版本中表现一致并与 Java 8 中的行为兼容:

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

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

Java 7 及之前的行为

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

关于java - 为什么在 Java 8 split 中有时会删除结果数组开头的空字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33919304/

相关文章:

java - MATLAB Java 类

java - ActionBar 出现空指针异常

java - 如何在每个 Arraylist 项目中删除\n

javascript - 替换javascript中最后出现的单词

string - 在Lua中分割字符串并打印选定的键

java - 获取近似平方根

java - SELECT 中的多个值

c - 如何将一个模式与多行相匹配并显示出来?

regex - 字符串在数字单词模式上分割

php - 如果特定的正则表达式匹配则修改字符串