【Java实用技术】字符串的拆分用什么方法好?有一半程序员都掉过split坑

必备字符串操作

我们将字符串操作分为下面6种:

  1. 基本操作方法
  2. 字符串判空、比较
  3. 字符串截取和拆分
  4. 字符串查找和替换
  5. 字符串和其他类型数据的转换
  6. 字符串拼接和格式化

今天我们来讲解第3.2节。


1、字符串拆分原生方法

对于字符串拆分的原生操作

//字符串拆分原生方法:
public String[] String.split(String regex)

问题:对字符串中含有“|”分割符部分进行拆分,怎么写?

直接str.split("|")可以吗?当然可以,但是结果是不是你想要的呢?

String str = "a.|b.";
*** 原生 String.split 方法结果 *****
[a, ., |, b, .]

答案:如果只是单纯想按照“|”分割,正确的写法是ss.split("\\|"),因为这里碰到烦人的正则表达式。

String str = "a.|b."; // 使用“|”拆分
System.out.println("*** 原生 String.split 方法 *****");
if (StringUtils.isNotEmpty(str)) {
    String[] s1 = str.split("\\|");
    System.out.println(Arrays.toString(s1));
}

split源码分析

// 选取源码中关键的分析

/**
围绕给定正则表达式的匹配拆分此字符串。
此方法的工作方式就像通过使用给定表达式和零限制参数调用双参数split方法一样。 因此,结果数组中不包含尾随空字符串。
参数:regex – 定界正则表达式
返回:通过围绕给定正则表达式的匹配拆分此字符串计算出的字符串数组
抛出:PatternSyntaxException – 如果正则表达式的语法无效
*/
public String[] split(String regex) {
    return split(regex, 0);
}

/**
围绕给定正则表达式的匹配拆分此字符串。
以str形式调用此方法。 split(regex , n)产生与表达式相同的结果:
Pattern.compile(regex).split(str , n)
*/
public String[] split(String regex, int limit) {
        /* 下面情况使用快速模式:
         (1)单个字符但是不能是正则中的元字符,比如".$|()[{^?*+\\"
         (2)两个字符,第一个字符是\,第二个字符不能是数字或者字母[0-9a-zA-Z]
         */
        char ch = 0;
        if (((regex.value.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))
        {
            //省略 使用substring拆分  过程
            String[] result = new String[resultSize];
            return list.subList(0, resultSize).toArray(result);
        }
       // 其他场景使用正则表达式拆分
        return Pattern.compile(regex).split(this, limit);
    }


源码不复杂,核心内容是:

(1)不是正则表达式的元字符".$|()[{^?*+\\"\开头但后面不是[0-9a-zA-Z]的使用快速拆分模式

(2)其他情况使用正则表达式匹配模式拆分

这里涉及到另外一个知识点,正则表达式,如果对正则表达式感兴趣的可以参考其他博文。

当然我认为你是没有兴趣的,而且很多程序员也很讨厌正则表达的规则,需要用的时候再翻阅。

对于(1),如果你十分精通正则表达式,可以很快知道哪些是正则元字符,你可以尝试使用原生的方法,毕竟使用方便且拆分速度确实快。

对于(2),因为正则表达式需要编译再匹配,比较耗时,所以不建议使用正则表达式。至于为什么正则表达式会慢,你就想你只见过一个姑娘一面就想找到她,和你知道她的名字地址具体信息比,哪个找到她更快?

综上所述,原生方法存在以下问题:

  • 使用前要判空
  • 小心正则表达式里面特殊字符
  • 性能不佳

为什么不推荐StringTokenizer

当然有同学可能会说jdk还有一个用于拆分字符串的类:StringTokenizer,它不是基于正则表达式进行拆分,性能应该高。但是笔者并不推荐使用此方法进行字符串拆分。我们先看源码:

 

/**
StringTokenizer is a legacy class that is retained for compatibility reasons although its use is 
discouraged
in new code. It is recommended that anyone seeking this functionality 
use the split method of String or the java.util.regex package instead.
Since: JDK1.0
*/
public class StringTokenizer implements Enumeration<Object> {
    //...
}


只看关键单词discouraged=不推荐。知道它是一个为了兼容而遗留下的类,官方不推荐使用就足够的。

另外一个不推荐的原因是:本来很简单的一个拆分,使用它需要搭配while循环,索性就不要用它了。


2、推荐字符串拆分操作

善用StringUtils.splitXX()

推荐使用Apache工具类StringUtils.splitXX()

优势:不用担心字符串为null(空),方法名直白。

推荐常用方法:

// 按照指定分割符拆分字符串
public static String[] split(String str, String separator)
 // 按照完整分割符拆分字符串
public static String[] splitByWholeSeparator(String str, String separator)
 // 拆分字符串,保留所有分割符位置 --较少使用
public static String[] splitPreserveAllTokens(String str, String separatorChars)

// 获取分割符的前部分
 public static String[] substringBefore(String str, String separatorChars)
 //获取分割符的后面部分
 public static String[] substringAfterLast(String str, String separatorChars)


对于上面问题按照“|”拆分,可以一行代码搞定。

String str = "a.|b.";
String[] s2 = StringUtils.split(str, "|");
/* 输出:[a., b.] */

开发中比较常见的需求是对", . | * : "等进行拆分。如果你对正则元字符不熟悉,很容易写出错误的拆分。

所以还是建议直接使用StringUtils工具类进行拆分,减少写代码时的思考成本。

StringUtils.split也有坑


还是上述例子,如果对字符串"a.|b.c"按照".|"进行拆分,使用StringUtils.split可以吗?

// 问题:请使用“.|”拆分
 String str = "a.|b.c";
String[] s2 = StringUtils.split(str, ".|");
/* 运行结果:[a, b, c]
并不是期望的[a, b.c] */

答案是不行,拆分的结果是[a, b, c],并不是期望的[a, b.c], 这是因为StringUtils.split方法是将多字符分割符拆成单个分割符进行拆分,再将非分割符部分返回。

如果希望对多字符分割符拆分,请使用StringUtils.splitByWholeSeparator()方法。

这个小细节,希望大家能够避坑。


3、demo用例


import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * Java实用技术课程 By Pandas.
 * 公众号:Java实用技术手册
 * JDK版本:jdk1.8.0_66
 *
 * @author Pandas
 * @date 2021/10/30
 */
public class StringSplitDemo {

    /**
     * 字符串拆分方法用哪个方法好?
     */
    public static void main(String[] args) {
        // 问题:请使用“|”拆分
        String str = "a.|b.c";
        System.out.println("*** 原生 String.split 方法 *****");
        if (StringUtils.isNotEmpty(str)) {
            String[] s1 = str.split("\\|");
            System.out.println(Arrays.toString(s1));
        }
        String[] s11 = StringUtils.split(str, "|");
        System.out.println(Arrays.toString(s11));

        System.out.println("*** 原生 StringTokenizer 方法 *****");
        StringTokenizer tokenizer = new StringTokenizer(str, "|");
        while (tokenizer.hasMoreTokens()) {
            System.out.println(tokenizer.nextToken());
        }

        // "a.|b.c"
        str = "a.|b.c";
        System.out.println("*** StringUtils 方法 *****");
        String[] s2 = StringUtils.split(str, ".|");
        System.out.println(Arrays.toString(s2));

        String[] s3 = StringUtils.splitByWholeSeparator(str, ".|");
        System.out.println(Arrays.toString(s3));

        str = "a.|b...";
        String[] s4 = StringUtils.splitPreserveAllTokens(str, ".");
        System.out.println(Arrays.toString(s4));
    }
}

/** 运行结果===>
*** 原生 String.split 方法 *****
[a., b.c]
[a., b.c]
*** 原生 StringTokenizer 方法 *****
a.
b.c
*** StringUtils 方法 *****
[a, b, c]
[a, b.c]
[a, |b, , , ]
*/


4、总结

  • 直接使用StringUtils最省事(注意上面写的避坑)。
  • 如果是对非特殊字符拆分,比如对字母数字拆分,可以使用原生的split方法。
  • 不要使用StringTokenizer。

感谢阅读本期内容,希望对新入行的你有帮助。

如果你意犹未尽,后面有一个字符串拆分姊妹篇,欢迎继续查看。


往期内容:


我是Pandas,专注Java编程实用技术分享,公众号Java实用技术手册和B站均有视频解说,欢迎来玩。

如果你觉得这篇文章有用,别忘了点赞+关注,一起进步!

上一篇:大数据行业图谱之(1):底层基础平台公司能做多大?


下一篇:开启你的大神之路-Android优质学习资源、项目和网站大整合(Android学习以来的全面资料整理)