正文

在日常的 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避坑的资料请关注Devmax其它相关文章!

java开发使用StringUtils.split避坑详解的更多相关文章

  1. Swift split

    转:http://www.cocoachina.com/bbs/read.PHP?tid=207275SwiftString扩展/**@String扩展*@Date:2014/06/17*/importCocoaextensionString{//分割字符funcsplit(s:String)->String[]{ifs.isEmpty{varx=String[]()foryinself{x.ap

  2. Swift 分割字符串

    直接上代码。

  3. split string with character in swift

    letsplitChar:String=””letlanArray:[String]=lans.componentsSeparatedByString(splitChar)

  4. Swift3.0 split函数切割字符串

    我们先看函数的原型:第一个参数就不用解释了,传入要切割的字符串,像这样下面看第二个参数,像这样,意思是切割几次,设置为1的话就把原来的字符串切成两个。第三个参数就很明确了,是否保留隐藏字符看看官方文档对这三个参数的解释,懒得翻译了,也不是很难懂。

  5. swift -- 定义空字符串 hasPrefix hasSuffix trim split join range

    //定义空的字符串varstr1=""varstr2=String()str1.isEmpty//判断字符串是否为空//输出字符串中所有的字符varstr3="Asgodname"forcinstr3{println}Int.max//Int类型的最大值Int.min//Int类型的最小值vararr1=["c","oc","ios","swift"]varcount=0fornameinarr1{ifname.hasPrefix("i"){//hasPrefix前缀count++count}}count

  6. 迅速 – 分裂现在抱怨失踪“isSeparator”

    在Swift1.2的最新升级之后,我无法弄清楚如何将一行文字分割成单词.我曾经这样做:但是,这不再工作,因为…嗯,好的,尽管我可以继续建设?好吧,我们试试看吧那么我可以想到的每个其他版本都会说:让我们听听Beta版测试新的编程语言!任何人知道正确的秘密酱1.2?Swift1.2中的参数似乎有所改变:或使用默认值:谓词现在是最后一个参数,需要一个外部参数参数名称isSeparator,因为它的前面是可选参数.这种变化的优点是您可以使用尾随关闭句法:要么

  7. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  8. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  9. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  10. Java实现世界上最快的排序算法Timsort的示例代码

    Timsort 是一个混合、稳定的排序算法,简单来说就是归并排序和二分插入排序算法的混合体,号称世界上最好的排序算法。本文将详解Timsort算法是定义与实现,需要的可以参考一下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部