split()函数的基本功能

根据给定正则表达式的匹配拆分此字符串。

先观察一些现象

请先阅读代码,并预测代码的结果。

第一段代码

public static void main(String[] args) {
        String s = "aba";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 2
0(1): a
1(1): a

结果共分为三个部分:括号前的数字表示序号;括号内的数字表示对应字符串的长度;冒号后即为对应的字符串。
结果符合我们对于split()函数的认知。

第二段代码

public static void main(String[] args) {
        String s = "abba";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 3
0(1): a
1(0): 
2(1): a

这里我们观察到,由于s中存在两个连着的字符b,因此split()在分割时会产生一个空字符串。

第三段代码

public static void main(String[] args) {
        String s = "ba";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 2
0(0): 
1(1): a

这里我们观察到,由于s中的字符b处于开头位置,因此split()在分割时会在第一个结果处放入一个空字符串。

第四段代码

public static void main(String[] args) {
        String s = "bba";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 3
0(0): 
1(0): 
2(1): a

这里我们观察到,结果中出现了两个空字符串,通过前面的分析,不难得出,第一个空字符串来自于s的第一个字符b,第二个空字符串来自于s中两个连续的字符b。

第五段代码

public static void main(String[] args) {
        String s = "ab";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 1
0(1): a

这里我们观察到,结果中仅有一个字符,这说明如果字符b处在s的末尾,则不会产生新的空字符串。

第六段代码

public static void main(String[] args) {
        String s = "abb";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }

根据前面的经验,两个连续的字符b会产生一个空字符串,结尾的b不会产生空字符串,但是结果并非如此。

结果
strings.length = 1
0(1): a

该结果与第五段代码的运行结果一致,两个连续的字符b并没有产生空字符串,说明相同字符串在结尾囤积并不会产生空字符串。为验证这一猜测,可以继续研究。

第七段代码

public static void main(String[] args) {
        String s = "babbabbb";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 4
0(0): 
1(1): a
2(0): 
3(1): a

这里我们观察到,结果中出现了两个空字符串,第一个空字符串来自于s的第一个字符b,第二个空字符串来自于s中两个连续的字符b。而结尾的字符b并没有产生更多的空字符串。基本验证这一假设。

第八段代码

public static void main(String[] args) {
        String s = "bbb";
        String[] strings = s.split("b");
        System.out.println("strings.length = " + strings.length);
        for (int i = 0; i < strings.length; i++) {
            String string = strings[i];
            System.out.println(i + "(" + string.length() + "): " + string);
        }
    }
结果
strings.length = 0

而执行结果告诉我们,如果s中的所有字符都被匹配时,split()会返回一个空数组。

源码分析

通过源码分析,我们可以更加深入的理解split()对于空字符串的处理。

public String[] split(String regex, int limit) {
        /* fastpath if the regex is a
         (1)one-char String and this character is not one of the
            RegEx's meta characters ".$|()[{^?*+\\", or
         (2)two-char String and the first char is the backslash and
            the second is not the ascii digit or ascii letter.
         */
        char ch = 0;
        if (((regex.length() == 1 &&
             ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
             (regex.length() == 2 &&
              regex.charAt(0) == '\\' &&
              (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
              ((ch-'a')|('z'-ch)) < 0 &&
              ((ch-'A')|('Z'-ch)) < 0)) &&
            (ch < Character.MIN_HIGH_SURROGATE ||
             ch > Character.MAX_LOW_SURROGATE))
        {
            int off = 0;
            int next = 0;
            boolean limited = limit > 0;
            ArrayList<String> list = new ArrayList<>();
            while ((next = indexOf(ch, off)) != -1) {
                if (!limited || list.size() < limit - 1) {
                    list.add(substring(off, next));
                    off = next + 1;
                } else {    // last one
                    //assert (list.size() == limit - 1);
                    int last = length();
                    list.add(substring(off, last));
                    off = last;
                    break;
                }
            }
            // If no match was found, return this
            if (off == 0)
                return new String[]{this};

            // Add remaining segment
            if (!limited || list.size() < limit)
                list.add(substring(off, length()));

            // Construct result
            int resultSize = list.size();
            if (limit == 0) {
                while (resultSize > 0 && list.get(resultSize - 1).isEmpty()) {
                    resultSize--;
                }
            }
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
        return Pattern.compile(regex).split(this, limit);
    }

我们关心的部分在于

if (limit == 0) {
    while (resultSize > 0 && list.get(resultSize - 1).isEmpty()) {
        resultSize--;
    }
}

其逻辑很明晰:在参数limit为0时,它会删除位于结尾的空字符串,而不传limit时,其数值默认为0。

public String[] split(String regex) {
    return split(regex, 0);
}

至此,逻辑被成功厘清。

阅读文档

实际上,在源码中关于split()函数的描述中又这样一句话:

Trailing empty strings are therefore not included in the resulting array.

大致的翻译为“因此,所得数组中不包括结尾空字符串”。
我们也可以在jdk的中文文档中找到这句话。