java开发使用StringUtils.split避坑详解

 更新时间:2022年11月04日 17:03:37   作者:程序猿阿朗  
这篇文章主要为大家介绍了java开发使用StringUtils.split避坑详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

正文

在日常的 Java 开发中,由于 JDK 未能提供足够的常用的操作类库,通常我们会引入 Apache Commons Lang 工具库或者 Google Guava 工具库简化开发过程。两个类库都为 java.lang API 提供了很多实用工具,比如经常使用的字符串操作,基本数值操作、时间操作、对象反射以及并发操作等。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

但是,最近在使用 Apache Commons Lang 工具库时踩了一个坑,导致程序出现了意料之外的结果。

StringUtils.split 的坑

也是因为踩了这个坑,索性写下一篇文章好好介绍下 Apache Commons Lang 工具库中字符串操作相关 API。

先说坑是什么,我们都知道 String 类中到的 split 方法可以分割字符串,比如字符串 aabbccdd 根据 bc 分割的结果应该是 aab 和 cdd 才对,这样的结果也很容易验证。

String str = "aabbccdd";
for (String s : str.split("bc")) {
    System.out.println(s);
}
// 结果
aab
cdd

可能是因为 String 类中的 split 方法的影响,我一直以为 StringUtils.split 的效果应该相同,但其实完全不同,可以试着分析下面的三个方法输出结果是什么,StringUtils 是 Commons Lang 类库中的字符串工具类。

 public static void testA() {
    String str = "aabbccdd";
    String[] resultArray = StringUtils.split(str, "bc");
    for (String s : resultArray) {
        System.out.println(s);
    }
}

我对上面 testA 方法的预期是 aab 和 cdd ,但是实际上这个方法的运行结果是:

// testA 输出
aa
dd

可以看到 b 和 c 字母都不见了,只剩下了 a 和 b,这是已经发现问题了,查看源码后发现 StringUtils.split 方法其实是按字符进行操作的,不会把分割字符串作为一个整体来看,返回的结果中不也会包含用于分割的字符。

验证代码:

public static void testB() {
    String str = "abc";
    String[] resultArray = StringUtils.split(str, "ac");
    for (String s : resultArray) {
        System.out.println(s);
    }
}
// testB 输出
b
public static void testC() {
    String str = "abcd";
    String[] resultArray = StringUtils.split(str, "ac");
    for (String s : resultArray) {
        System.out.println(s);
    }
}
// testC 输出
b
d

输出结果和预期的一致了。

StringUtils.split 源码分析

点开源码一眼看下去,发现在方法注释中就已经进行提示了:返回的字符串数组中不包含分隔符

The separator is not included in the returned String array. Adjacent separators are treated as one separator. For more control over the split use the StrTokenizer class....

继续追踪源码,可以看到最终 split 分割字符串时入参有四个。

private static String[] splitWorker(
final String str, // 原字符串 
final String separatorChars,  // 分隔符
final int max,  // 分割后返回前多少个结果,-1 为所有
final boolean preserveAllTokens // 暂不关注
) {
}

根据分隔符的不同又分了三种情况。

1. 分隔符为 null

final int len = str.length();
if (len == 0) {
    return ArrayUtils.EMPTY_STRING_ARRAY;
}
final List<String> list = new ArrayList<>();
int sizePlus1 = 1;
int i = 0;
int start = 0;
boolean match = false;
boolean lastMatch = false;
if (separatorChars == null) {
    // Null separator means use whitespace
    while (i < len) {
        if (Character.isWhitespace(str.charAt(i))) { 
            if (match || preserveAllTokens) {
                lastMatch = true;
                if (sizePlus1++ == max) {
                    i = len;
                    lastMatch = false;
                }
                list.add(str.substring(start, i));
                match = false;
            }
            start = ++i;
            continue;
        }
        lastMatch = false;
        match = true;
        i++;
    }
}
// ...
if (match || preserveAllTokens && lastMatch) {
            list.add(str.substring(start, i));
}

可以看到如果分隔符为 null ,是按照空白字符 Character.isWhitespace() 分割字符串的。分割的算法逻辑为:

a. 用于截取的开始下标置为 0 ,逐字符读取字符串。

b. 碰到分割的目标字符,把截取的开始下标到当前字符之前的字符串截取出来。

c. 然后用于截取的开始下标置为下一个字符,等到下一次使用。

d. 继续逐字符读取字符串、

2. 分隔符为单个字符

逻辑同上,只是判断逻辑 Character.isWhitespace() 变为了指定字符判断。

// Optimise 1 character case
final char sep = separatorChars.charAt(0);
while (i < len) {
    if (str.charAt(i) == sep) { // 直接比较
      ...

3. 分隔符为字符串

总计逻辑同上,只是判断逻辑变为包含判断。

 // standard case
while (i < len) {
    if (separatorChars.indexOf(str.charAt(i)) >= 0) { // 包含判断
        if (match || preserveAllTokens) {

如何解决?

1. 使用 splitByWholeSeparator 方法。

我们想要的是按整个字符串分割,StringUtils 工具类中已经存在具体的实现了,使用 splitByWholeSeparator 方法。

String str = "aabbccdd";
String[] resultArray = StringUtils.splitByWholeSeparator(str, "bc");
for (String s : resultArray) {
    System.out.println(s);
}
// 输出
aab
cdd

2. 使用 Google Guava 工具库

关于 Guava 工具库的使用,之前也写过一篇文章,可以参考:Guava - 拯救垃圾代码

String str = "aabbccdd";
Iterable<String> iterable = Splitter.on("bc")
    .omitEmptyStrings() // 忽略空值
    .trimResults() // 过滤结果中的空白
    .split(str);
iterable.forEach(System.out::println);
// 输出
aab
cdd

3. JDK String.split 方法

使用 String 中的 split 方法可以实现想要效果。

String str = "aabbccdd";
String[] res = str.split("bc");
for (String re : res) {
    System.out.println(re);
}
// 输出
aab
cdd

但是 String 的 split 方法也有一些坑,比如下面的输出结果。

String str = ",a,,b,";
String[] splitArr = str.split(",");
Arrays.stream(splitArr).forEach(System.out::println);
// 输出
a
b

开头的逗号,前出现了空格,末尾的逗号,后却没有空格。

一如既往,文章中代码存放在 Github.com/niumoo/javaNotes.

以上就是java开发使用StringUtils.split避坑详解的详细内容,更多关于java开发StringUtils.split避坑的资料请关注脚本之家其它相关文章!

相关文章

  • Java线程的异常处理机制详情

    Java线程的异常处理机制详情

    这篇文章主要介绍了Java线程的异常处理机制详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下
    2022-07-07
  • SpringBoot 在测试时如何指定包的扫描范围

    SpringBoot 在测试时如何指定包的扫描范围

    这篇文章主要介绍了SpringBoot 在测试时如何指定包的扫描范围,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • Java实现的n阶曲线拟合功能示例

    Java实现的n阶曲线拟合功能示例

    这篇文章主要介绍了Java实现的n阶曲线拟合功能,结合实例形式分析了Java基于矩阵的多项式曲线拟合相关操作技巧,需要的朋友可以参考下
    2018-01-01
  • Java编译和解释执行对比及原理解析

    Java编译和解释执行对比及原理解析

    这篇文章主要介绍了Java编译和解释执行对比及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-05-05
  • SpringBoot动态更新yml文件

    SpringBoot动态更新yml文件

    在系统运行过程中,可能由于一些配置项的简单变动需要重新打包启停项目,这对于在运行中的项目会造成数据丢失,客户操作无响应等情况发生,针对这类情况对开发框架进行升级提供yml文件实时修改更新功能,这篇文章主要介绍了SpringBoot动态更新yml文件
    2023-01-01
  • Spring Security实现多次登录失败后账户锁定功能

    Spring Security实现多次登录失败后账户锁定功能

    当用户多次登录失败的时候,我们应该将账户锁定,等待一定的时间之后才能再次进行登录操作。今天小编给大家分享Spring Security实现多次登录失败后账户锁定功能,感兴趣的朋友一起看看吧
    2019-11-11
  • 详解Servlet之过滤器(Filter)

    详解Servlet之过滤器(Filter)

    本篇文章主要介绍了Servlet——过滤器(Filter),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • SpringMVC中RequestBody注解的List参数传递方式

    SpringMVC中RequestBody注解的List参数传递方式

    这篇文章主要介绍了SpringMVC中RequestBody注解的List参数传递方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • Java编程实现从尾到头打印链表代码实例

    Java编程实现从尾到头打印链表代码实例

    这篇文章主要介绍了Java编程实现从尾到头打印链表代码实例,小编觉得挺不错的,这里分享给大家,供需要的朋友参考。
    2017-10-10
  • java分布式面试降级组件Hystrix的功能特性

    java分布式面试降级组件Hystrix的功能特性

    这篇文章主要为大家介绍了java分布式面试关于降级组件Hystrix的功能特性回答,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-03-03

最新评论