使用 StringUtils.split 的坑

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

使用 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 分割的结果应该是 aabcdd 才对,这样的结果也很容易验证 。
String str = "aabbccdd";for (String s : str.split("bc")) {System.out.println(s);}// 结果aabcdd可能是因为 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 方法的预期是 aabcdd ,但是实际上这个方法的运行结果是:
// testA 输出aadd可以看到 bc 字母都不见了,只剩下了 ab,这是已经发现问题了,查看源码后发现 StringUtils.split 方法其实是按字符进行操作的 , 不会把分割字符串作为一个整体来看 , 返回的结果中不也会包含用于分割的字符 。
验证代码:
public static void testB() {String str = "abc";String[] resultArray = StringUtils.split(str, "ac");for (String s : resultArray) {System.out.println(s);}}// testB 输出bpublic static void testC() {String str = "abcd";String[] resultArray = StringUtils.split(str, "ac");for (String s : resultArray) {System.out.println(s);}}// testC 输出bd输出结果和预期的一致了 。
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 whitespacewhile (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() 变为了指定字符判断 。

推荐阅读