使用 StringUtils.split 的坑
点赞再看,动力无限。 微信搜「程序猿阿朗 」。
本文 Github.com/niumoo/JavaNotes 和 未读代码博客 已经收录,有很多知识点和系列文章。

在日常的 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.
<完>
文章持续更新,可以微信搜一搜「 程序猿阿朗 」或访问「程序猿阿朗博客 」第一时间阅读。本文 Github.com/niumoo/JavaNotes 已经收录,有很多知识点和系列文章,欢迎Star。
使用 StringUtils.split 的坑的更多相关文章
- String.split()与StringUtils.split()
我们平时进行简单的字符串分割的时候,尽量不要用String自身的split方法,它是匹配正则表达式的,如果遇到$这种特殊字符,需要转义一下.用StringUtils.split()方法会更方便 使用a ...
- StringUtils.split()和string.split()的区别
场景 出于业务考虑,将多个字符串拼接起来时,使用的分隔符是;,;.如果要将这样一个拼接来的字符串分割成原本的多个字符串时,就需要使用到jdk自带的split()方法.不过因为公司的编程规范,改为使用了 ...
- String.split()与StringUtils.split()的区别
import com.sun.deploy.util.StringUtils; String s =",1,,2,3,4,,"; String[] split1 = s.split ...
- java的split的坑,会忽略空值
String test = "@@@@"; String[] arrayTest = test.split("\\@"); System.out.println ...
- python2和python3中split的坑
执行同样的split,python2和python3截取的行数内容却不一样 我想要截取Dalvik Heap行,使用split('\n')的方法 import os cpu='adb shell du ...
- java 字符串split有很多坑,使用时请小心!!
System.out.println(":ab:cd:ef::".split(":").length);//末尾分隔符全部忽略 System.out.print ...
- JAVA StringUtils 坑汇总
1 StringUtils.split() VS String.split(); public static void main(String args[]){ String r ...
- Mac下hadoop运行word count的坑
Mac下hadoop运行word count的坑 Word count体现了Map Reduce的经典思想,是分布式计算中中的hello world.然而博主很幸运地遇到了Mac下特有的问题Mkdir ...
- JAVA踩坑录
以前踩了很多坑,大多忘了.现在踩了坑,想起了一定记下来. 1. 字符串分割,这种工具类,首次使用一定要先看一眼,不然跳坑 commons-lang StringUtils.split分割时会去掉空串: ...
随机推荐
- Debezium的基本使用(以MySQL为例)
GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. GreatSQL是MySQL的国产分支版本,使用上与MySQL一致. 一.Debezium介绍 摘自官网: Debeziu ...
- Unity 将是驱动 C# 增长的引擎吗 ?
C# 在中国的采用需要一个杀手级应用的带动, 那么这样的一个杀手级应用是 Unity吗,我这里大胆推测采用CoreCLR 的新一代完全采用C#构建的Unity 将是这样的一个杀手级应用.Unity已被 ...
- [CSP-S 2019 day2 T1] Emiya家今天的饭
题面 题解 不考虑每种食材不超过一半的限制,答案是 减去 1 是去掉一道菜都不做的方案. 显然只可能有一种菜超过一半,于是枚举这种菜,对每个方式做背包即可(记一维状态表示这种菜比别的菜多做了多少份). ...
- Codeforces Round #585 (Div. 2) E. Marbles (状压DP),BZOJ大理石(同一道题)题解
题意 林老师是一位大理石收藏家,他在家里收藏了n块各种颜色的大理石,第i块大理石的颜色为ai.但是林老师觉得这些石头在家里随意摆放太过凌乱,他希望把所有颜色相同的石头放在一起.换句话说,林老师需要对现 ...
- Tablesaw——Java统计、机器学习库
资源 java二维数组处理可可视化库 https://github.com/jtablesaw/tablesaw plotly JS库的Java封装 https://github.com/jtable ...
- flutter系列之:UI layout简介
目录 简介 flutter中layout的分类 常用layout举例 总结 简介 对于一个前端框架来说,除了各个组件之外,最重要的就是将这些组件进行连接的布局了.布局的英文名叫做layout,就是用来 ...
- KingbaseES 支持pivot and unpivot 功能
KingbaseES 通过扩展插件支持了pivot 和unpivot 功能.以下以例子的方式介绍. 一.功能介绍 创建扩展: create extension kdb_utils_function; ...
- git merge和git rebase总结
dev分支 * da349ef (dev) e * 75350bc d * 63cbbb8 c * c6509a5 b * 13405af a 文件可能会发生冲突,需要解决一下 aaaaaaaaa b ...
- 在UniApp的H5项目中,生成二维码和扫描二维码的操作处理
在我们基于UniApp的H5项目中,需要生成一些二维码进行展示,另外也需要让用户可以扫码进行一定的快捷操作,本篇随笔介绍一下二维码的生成处理和基于H5的扫码进行操作.二维码的生成,使用了JS文件wea ...
- java~springboot(2022之后)~目录索引
回到占占推荐博客索引 最近写了不过关于java,spring,微服务的相关文章,今天把它整理一下,方便大家学习与参考. java~springboot(2022之前)~目录索引 java~spring ...