Java split函数的坑

Posted nxlhero

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java split函数的坑相关的知识,希望对你有一定的参考价值。

代码中使用了split,结果分割后的数组长度不固定,访问的时候出现了ArrayIndexOutOfBoundsException,代码差不多是下面这样的。

public class Test {
    public static void main(String[] args) throws InterruptedException {
        //String a = "a|b|c|d|e|f";
        String a = "a|||||";
        String[] b = a.split("\\\\|");
        System.out.println(b[3]);
    }
}

测试环境没有测到包含很多空串的情况,结果生产上出现了,这时候出了问题。解决问题的方法也很简单,用split(str, -1)就可以。

split这个方法用的比较多,但是也有一些坑,我们从jdk的注释中去把它的用法好好理一下。

看一下split的代码,split(String regrex),实际上是调用了split(String regrex, int limit),直接看后者。

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

参数:

regex the delimiting regular expression

limit the result threshold, as described above

要完全掌握split,把这两个参数理解了就可以了。

参数1:正则表达式字符串

第一个参数是字符串类型,但是它代表的并不是字符串,而是一个正则表达式。所以你传入了一个字符串,并不一定以这个字符串分割,大部分情况下先用这个字符串生成一个正则表达式,再按正则表达式去分割。

java.String.split(regrex,n)运行的效果就相当于java.util.regex.Pattern.compile(regex).split(str,n)

看下代码,稍微有点差别,就是特定情况下用字符串操作,否则就使用正则表达式,用正则表达式的性能会差一些。

    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 (fastPath) {
            //使用快捷方法
        }
        return Pattern.compile(regex).split(this, limit);
    }

符合哪两个条件的话会用快捷方法呢?

1) 只有一个字符,并且不是正则表达式的元字符,也就是.$|()[{^?*+\\。

2) 有两个字符,并且第一个字符是\\, 第二个字符是非数字非字母的ascii。

只有一个字符的情况,不用正则表达式,直接用字符串操作,效率是最高的,而且使用场景也比较多,所以单独拿出来了,前提是不是特殊字符。而第二种情况,为什么限定后面必须是非数字非字母呢? 因为\\后面加上字母或者数字,有特殊含义。

另外,在Java中,"\\"是一个特殊作用的字符,包括在正则表达式里,就是转义,要在字符串里包含"\\",必须在前面再加个"\\",否则编译就不通过。

我们用代码来展示一下参数1的一些情况。

public class Test {

    public static void main(String[] args) throws InterruptedException {

        String a = "abc(|5|@|";

        // 反斜线加非数字或字母ascii字符,而且不是正则表达式元字符,相当于反斜线没用,走fastpath
        String[] b = a.split("@");   // [abc(|5|, |]
        String[] c = a.split("\\\\@"); // [abc(|5|, |]

        // 单个字母走fastpath,加上反斜线,则按正则表达式处理
        // 可能不是一个正确的正则表达式,抛异常,也可能是一个特定含义的正则表达式
        String[] d = a.split("c");     // [ab, (n|5|@|]
        //String[] e = a.split("\\\\c"); // 抛异常 PatternSyntaxException
        String[] f = a.split("\\\\d");   // [abc(|, |@|] , \\d表示匹配数字

        // 单个数字走fastpath,加上反斜线,是一个正规的正则表达式
        String[] g = a.split("5");  // [abc(|, |@|]
        String[] h = a.split("\\5"); // [abc(|5|@|],在下面简单介绍一下这种用法

        // 单个|,是一个特定的正则表达式,|在正则表达式里一般表示“或”,比如a|5就是a或者5,
        // 如果只有一个|,可以看成"空|空",所以相当于分成单个字符
        // 如果使用它本身的含义,则必须前边加转义,也就是\\|,这样正则表达式引擎会把它当成|
        String[] i = a.split("|");   // [a, b, c, (, |, 5, |, @, |]
        String[] j = a.split("a|5"); // [, bc(|, |@|]
        String[] k = a.split("\\\\|"); // [abc(, 5, @]
        //String[] l = a.split("("); //抛异常

        print(b);       
    }

    public static void print(String[] x) {  
        System.out.println(Arrays.asList(x));
    }
}

关于正则表达式,太复杂了,此处就不再展开了,其实用split,一般都是固定的字符串,不会涉及到正则,但是这些使用上的细节需要注意。

参数2: limit匹配限制

limit的值 含义
大于0 最多匹配limit-1次,次数到了,最后面的作为一个整体返回,数组长度不会超过limit
等于0 一直匹配到结束,但是如果后面全是空串,则丢弃
小于0 一直匹配到结束,不丢弃空串

我们来看一下实例

public class Test {
    public static void main(String[] args) throws InterruptedException {                
        String a = "a||c||||";
        print(a.split("\\\\|",2));
        print(a.split("\\\\|",100));
        print(a.split("\\\\|"));
        print(a.split("\\\\|",-1));
        print(a.split("\\\\|",-100));     
    }
    public static void print(String[] x) {  
        System.out.println(Arrays.asList(x));
    }
}

结果如下:

[a, |c||||]
[a, , c, , , , ]
[a, , c]
[a, , c, , , , ]
[a, , c, , , , ]

首先,如果是小于0,那么-1跟-100是一样的,而大于0的情况,就严格按匹配次数,唯独是0的情况,比较特殊,这也是比较坑的地方。它会把后面的空串全部丢弃,原话是这样的and trailing empty strings will be discarded,这个比较坑,大于0的情况是不会丢弃后面空串的。

所以,下面的用法,结果就是一样的,

public class Test {
    public static void main(String[] args) throws InterruptedException {                
        String a = "||||||e";
        print(a.split("\\\\|"));
        print(a.split("\\\\|",-1));
    }
    public static void print(String[] x) {  
        System.out.println(Arrays.asList(x));
    }
}

结果是

[, , , , , , e]
[, , , , , , e]

只要后面没有空串,那split(str)和split(str,-1)是一样的。

注意,两个竖线中间的不是null,而是空串。

public class Test {
    public static void main(String[] args) throws InterruptedException {                
        String a = "||||||e";   
        String[] b = a.split("\\\\|");
        if(b[0].compareTo("") == 0) {
            System.out.println("empty string");
        }
    }
}

附:JDK中的注释

Splits this string around matches of the given regular expression.

The array returned by this method contains each substring of this string that is terminated by another substring that matches the given expression or is terminated by the end of the string. The substrings in the array are in the order in which they occur in this string. If the expression does not match any part of the input then the resulting array has just one element, namely this string.

The limit parameter controls the number of times the pattern is applied and therefore affects the length of the resulting array. If the limit n is greater than zero then the pattern will be applied at most n - 1 times, the array\'s length will be no greater than n, and the array\'s last entry will contain all input beyond the last matched delimiter. If n is non-positive then the pattern will be applied as many times as possible and the array can have any length. If n is zero then the pattern will be applied as many times as possible, the array can have any length, and trailing empty strings will be discarded.

The string "boo:and:foo", for example, yields the following results with these parameters:

An invocation of this method of the form str.split(regex, n) yields the same result as the expression

  • Parameters:

    regex the delimiting regular expression

    limit the result threshold, as described above

  • Returns:

    the array of strings computed by splitting this string around matches of the given regular expression

  • Throws:

    [PatternSyntaxException](eclipse-javadoc:☂=transactioninsight/C:\\/Program Files\\/Java\\/jdk1.7.0_80\\/jre\\/lib\\/rt.jar=/maven.pomderived=/true=/=/javadoc_location=/https:\\/\\/docs.oracle.com\\/javase\\/7\\/docs\\/api\\/=/ - if the regular expression\'s syntax is invalid

  • Since:

    1.4

  • See Also:

    [java.util.regex.Pattern](eclipse-javadoc:☂=transactioninsight/C:\\/Program Files\\/Java\\/jdk1.7.0_80\\/jre\\/lib\\/rt.jar=/maven.pomderived=/true=/=/javadoc_location=/https:\\/\\/docs.oracle.com\\/javase\\/7\\/docs\\/api\\/=/

  • @spec

    JSR-51

以上是关于Java split函数的坑的主要内容,如果未能解决你的问题,请参考以下文章

使用 StringUtils.split 的坑

kotlin中split的坑

kotlin中split的坑

java Ftp上传创建多层文件的代码片段

java神坑系列——split

java split函数报错