好好学Java:从零基础到项目实战
上QQ阅读APP看书,第一时间看更新

5.3.1 利用正则串分割字符串

除了前述的字符串处理方法外,还有一种分割字符串的场景也很常见,就是按照某个规则将字符串切割为若干子串。分割规则通常是指定某个分隔符,根据字符串内部的分隔符将字符串分割,例如逗号、空格等都可以作为字符串的分隔符。正好String类型提供了split方法用于切割字符串,只要字符串变量调用split方法,并把分隔符作为输入参数,该方法即可返回分割好的字符串数组。

下面的split调用代码例子将演示如何按照逗号和空格切割字符串(完整代码见本章源码的src\com\string\regular\RegexSplit.java):

    // 通过逗号分割字符串
    private static void splitByComma() {
        String commaStr="123,456,789";
        String[] commaArray=commaStr.split(","); //利用split方法指定按照逗号切割字符串
        for (String item : commaArray) {  //遍历并打印分割后的字符串数组元素
            System.out.println("comma item=" + item);
        }
    }
 
    // 通过空格分割字符串
    private static void splitBySpace() {
        String spaceStr="123 456 789";
        String[] spaceArray=spaceStr.split(" "); //利用split方法指定按照空格切割字符串
        for (String item : spaceArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("space item=" + item);
        }
    }

除了逗号和空格以外,点号和竖线也常常用来分隔字符串,但是对于点号和竖线,split方法的调用代码不会得到预期的结果。相反,split(".")无法得到分割后的字符串数组,也就是说结果的字符串数组为空;而split("|")分割得到的字符串数组,每个数组元素只有一个字符,其结果类似于toCharArray。究其原因,缘于split方法的输入参数理应是一个正则串,并非普通的分隔字符。由于点号和竖线都是正则串的保留字符,因此无法直接在正则串中填写,必须转义处理方可。如同回车符和换行符在普通字符串中通过前缀的反斜杆转义那样(回车符对应\r,换行符对应\n),正则字符串通过在原字符前面添加两个反斜杆来转义,像点号字符在正则串中对应的转义符为“\\.”,而竖线在正则串中对应的转义符为“\\|”。

经过转义处理之后,通过点号和竖线切割字符串的正确写法如下:

    // 通过点号分割字符串
    private static void splitByDot() {
        String dotStr="123.456.789";
        // split(".")无法得到分割后的字符串数组
        //String[] dotArray=dotStr.split(".");
        // 点号是正则串的保留字符,需要转义(在点号前面加两个反斜杆)
        String[] dotArray=dotStr.split("\\.");
        for (String item : dotArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("dot item="+item);
        }
    }
 
    // 通过竖线分割字符串
    private static void splitByLine() {
        String lineStr="123|456|789";
        // split("|")分割得到的字符串数组,每个数组元素只有一个字符,类似于toCharArray的结果
        //String[] lineArray=lineStr.split("|");
        // 竖线是正则串的保留字符,需要转义(在竖线前面加两个反斜杆)
        String[] lineArray=lineStr.split("\\|");
        for (String item : lineArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("line item="+item);
        }
    }

竖线符号之所以被定为正则串的保留字符,是因为它在正则表达式里起到了“或”的判断作用,例如正则串“,| ”表示逗号和空格都是满足条件的分隔符。一个字符串如果同时包含一个逗号和一个空格,那么按照“,| ”切割的结果将是长度为3的字符串数组。也就是说,原始串被逗号分割一次后又被空格分割一次,这样一共分割两次,最终得到了3个子串。下面的代码将演示使用正则串“,| ”切割字符串的效果:

    // 利用竖线同时指定多个串来分割字符串
    private static void splitByMixture() {
        String mixtureStr="123,456 789";
        // 正则串里的竖线表示“或”,竖线左边和右边的字符都可以用来分割字符串
        String[] mixtureArray=mixtureStr.split(",| ");
        for (String item : mixtureArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("mixture item="+item);
        }
    }

当然,正则串中的保留字符不仅包括点号和竖线,还包括好多常见的符号,比如加号(+)、星号(*)、横线(-),在正则串中均需转义。其中,加号的正则转义符为“\\+”,星号的正则转义符为“\\*”,横线的正则转义符为“\\-”。这样来看,加减乘除的四则运算符号只有除法的斜杆符(/)、取余数的百分号(%)无须转义处理。倘若有一个字符串,要求以四则运算的5个符号进行切割,则需通过竖线把这几个转义后的字符加以连接,构成形如“\\+|\\*|\\-|/|%”的正则串。于是按照加减乘除与取余数符号切割字符串的代码就变为下面这样:

    // 通过算术的加减乘除符号来分割字符串
    private static void splitByArith() {
        String arithStr="123+456*789-123/456%789";
        // 正则串里的加号、星号、横线都要转义,加减乘除与取余数符号之间通过竖线隔开
        String[] arithArray=arithStr.split("\\+|\\*|\\-|/|%");
        for (String item : arithArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("arith item="+item);
        }
    }

分割用的正则串不单单是一个个字符,还支持好几个字符组成的字符串,譬如“(1)”“(2)”“(3)”都可以作为分隔串。注意圆括号内部的数字可以是0~9。如此一来,“(0)”~“(9)”的分隔串合集岂不是要写成这般:“(0)|(1)|(2)|(3)|(4)|(5)|(6)|(7)|(8)|(9)”?然而以上正则串的写法有两个错误:

(1)圆括号是正则表达式的保留字符,所以不能直接在正则串中书写“(”和“)”,而必须写成转义形式“\\(”和“\\)”。

(2)作为保留字符的圆括号,其作用类似于数值计算时的圆括号,都是通过圆括号把括号内外的运算区分开的。而竖线符号“|”的“或”运算优先级不如圆括号,因此每逢复杂一点的“或”运算,应当把圆括号放在整个逻辑运算式子的外面。

综合以上两点,修正之后的正则串应该改成:“\\((0|1|2|3|4|5|6|7|8|9)\\)”。但是该式子的竖线太多,只是用于获取0~9之间的某个数字之一。针对这种情况,正则表达式引入了另外一种简化的写法,即通过方括号包裹0123456789,形如“\\([0123456789]\\)”,同样指代0~9之间的某个数字,从而省略了若干个竖线。进一步说,日常生活中的0到9,常常写作“0-9”,于是对应更简单的正则串“\\([0-9]\\)”。

其实0~9正好涵盖了所有的一位数字,对于一位数字而言,正则表达式提供了专门的表达式“\\d{1}”。式子前面的“\\d”代表某个数字,式子后面的“{1}”代表字符数量是1位。推而广之,“\\d{2}”表示两位数字,“\\d{3}”表示三位数字,等等。像这个正则例子只有一位数字,甚至尾巴的“{1}”都可以去掉,因为“\\d”默认就是一位数字。

前面介绍了多种从0~9的正则表达串,接下来不妨逐一验证这些正则串是否有效,验证用的代码例子如下:

        // 通过圆括号及其内部数字来分割字符串
    private static void splitByBracket() {
        String bracketStr="(1)123;(2)456;(3)789;";
        // 圆括号也是正则串的保留字符,0~9这10个数字使用竖线隔开
        //String[] bracketArray=bracketStr.split ("\\((0|1|2|3|4|5|6|7|8|9)\\)");
        // 利用方括号聚集一群字符,表示这些字符之间是“或”的关系,故而可省略竖线
        //String[] bracketArray=bracketStr.split("\\([0123456789]\\)");
        // 连续的数字可使用横线连接首尾数字,例如“0-9”表示从0~9之间的所有数字
        //String[] bracketArray=bracketStr.split("\\([0-9]\\)");
        // 利用“\\d”即可表达0~9的数字,后面的{1}表示1位数字,以此类推,{3}表示三位数字
        //String[] bracketArray=bracketStr.split("\\(\\d{1}\\)");
        // “\\d”默认就是1位数字,此时后面的{1}可直接略去
        String[] bracketArray=bracketStr.split("\\(\\d\\)");
        for (String item : bracketArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("bracket item="+item);
        }
    }

上述的几种正则串只能表达从“(0)”~“(9)”的分隔串,然而圆括号内部还可能是两位数字或者三位数字,比如“(10)”“(12)”“(001)”。对于数字位数不固定的情况,可以把“\\d”改为“\\d+”,末尾多出来的加号表示前面的字符允许有一位,也允许有多位。此时正则串添加了加号的字符串切割代码如下:

    // 通过特殊符号的加号来分割字符串
    private static void splitWithPlus() {
        String bracketStr="(1)123;(2)456;(13)789;";
        // 正则串里的加号表示可以有一到多个前面的字符
        String[] bracketArray=bracketStr.split("\\(\\d+\\)");
        for (String item : bracketArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("plus item="+item);
        }
    }

上面介绍的字符位数不固定,毕竟至少还有一位。假设现在某个字符不但位数不确定,甚至还可能没有该字符(位数为0),采用写法“\\d+”就无法奏效了。要想满足位数可有可无的情况,需将末尾的加号换成星号,也就是改成\\d*。此时改用星号的字符串切割代码变为下面这般:

    // 通过特殊符号的星号来分割字符串
    private static void splitWithStar() {
        String bracketStr="()123;(2)456;(13)789;";
        // 正则串里的星号表示可以有0到多个前面的字符
        String[] bracketArray=bracketStr.split("\\(\\d*\\)");
        for (String item : bracketArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("star item="+item);
        }
    }

截至目前,分隔符还仅限于标点和数字,如果引入英文字母作为分隔串,又该如何书写呢?注意英文字母区分大小写,因而使用“a-z”表示所有的小写字母,使用“A-Z”表示所有的大写字母。如果采纳“(a)”“(B)”“(c)”这种大小写混合的分隔串,就得通过正则串“\\([a-zA-Z]\\)”来表达,对应的分割字符串代码如下:

    // 通过大小写字母来分割字符串
    private static void splitWithLetter() {
        String bracketStr="(a)123;(B)456;(c)789;";
        // 在正则串中表达小写字母和大写字母
        String[] bracketArray=bracketStr.split("\\([a-zA-Z]\\)");
        for (String item : bracketArray) {
            System.out.println("letter item="+item);
        }
    }

现在有一个麻烦的业务场景,圆括号内部不但可能是数字和字母,还可能是其他标点符号,这时难不成把众多标点符号一个一个罗列出来?要知道标点符号并没有“0-9”“a-z”“A-Z”的简单写法。不过这难不倒强大的正则表达式,因为点号作为正则的保留字符,它代表了除回车“\r”和换行“\n”以外的其他字符,所以使用“\\(.\\)”即可表达符合要求的任意字符。当然,这是被圆括号包裹着的除了回车“\r”和换行“\n”以外的任意字符。下面便是匹配前述场景的分割字符串代码例子:

    // 通过特殊符号的点号来分割字符串
    private static void splitWithDot() {
        String bracketStr="(1)123;(B)456;(%)789;";
        // 正则串里的点号表示除了回车“\r”和换行“\n”以外的其他字符
        String[] bracketArray=bracketStr.split("\\(.\\)");
        for (String item : bracketArray) {  // 遍历并打印分割后的字符串数组元素
            System.out.println("dot item="+item);
        }
    }