正文
在日常的 Java 开发中,由于 JDK 未能提供足够的常用的操作类库,通常我们会引入 Apache Commons Lang 工具库或者 Google Guava 工具库简化开发过程。两个类库都为 java.lang
API 提供了很多实用工具,比如经常使用的字符串操作,基本数值操作、时间操作、对象反射以及并发操作等。
1 | < 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
才对,这样的结果也很容易验证。
1 2 3 4 5 6 7 | String str = "aabbccdd" ; for (String s : str.split( "bc" )) { System.out.println(s); } // 结果 aab cdd |
可能是因为 String 类中的 split
方法的影响,我一直以为 StringUtils.split
的效果应该相同,但其实完全不同,可以试着分析下面的三个方法输出结果是什么,StringUtils 是 Commons Lang 类库中的字符串工具类。
1 2 3 4 5 6 7 | 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
方法其实是按字符进行操作的,不会把分割字符串作为一个整体来看,返回的结果中不也会包含用于分割的字符。
验证代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 分割字符串时入参有四个。
1 2 3 4 5 6 7 | private static String[] splitWorker( final String str, // 原字符串 final String separatorChars, // 分隔符 final int max, // 分割后返回前多少个结果,-1 为所有 final boolean preserveAllTokens // 暂不关注 ) { } |
根据分隔符的不同又分了三种情况。
1. 分隔符为 null
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 </string> |
可以看到如果分隔符为 null
,是按照空白字符 Character.isWhitespace()
分割字符串的。分割的算法逻辑为:
a. 用于截取的开始下标置为 0 ,逐字符读取字符串。
b. 碰到分割的目标字符,把截取的开始下标到当前字符之前的字符串截取出来。
c. 然后用于截取的开始下标置为下一个字符,等到下一次使用。
d. 继续逐字符读取字符串、
2. 分隔符为单个字符
逻辑同上,只是判断逻辑 Character.isWhitespace()
变为了指定字符判断。
1 2 3 | // Optimise 1 character case final char sep = separatorChars.charAt( 0 ); while (i |
3. 分隔符为字符串
总计逻辑同上,只是判断逻辑变为包含判断。
1 2 3 | // standard case while (i = 0 ) { // 包含判断 if (match || preserveAllTokens) { |
如何解决?
1. 使用 splitByWholeSeparator
方法。
我们想要的是按整个字符串分割,StringUtils 工具类中已经存在具体的实现了,使用 splitByWholeSeparator
方法。
1 2 3 4 5 6 7 8 | String str = "aabbccdd" ; String[] resultArray = StringUtils.splitByWholeSeparator(str, "bc" ); for (String s : resultArray) { System.out.println(s); } // 输出 aab cdd |
2. 使用 Google Guava 工具库
关于 Guava 工具库的使用,之前也写过一篇文章,可以参考:Guava – 拯救垃圾代码
1 2 3 4 5 6 7 8 9 10 | String str = "aabbccdd" ; Iterable<string> iterable = Splitter.on( "bc" ) .omitEmptyStrings() // 忽略空值 .trimResults() // 过滤结果中的空白 .split(str); iterable.forEach(System.out::println); // 输出 aab cdd </string> |
3. JDK String.split 方法
使用 String 中的 split 方法可以实现想要效果。
1 2 3 4 5 6 7 8 | String str = "aabbccdd" ; String[] res = str.split( "bc" ); for (String re : res) { System.out.println(re); } // 输出 aab cdd |
但是 String 的 split 方法也有一些坑,比如下面的输出结果。
1 2 3 4 5 6 | 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避坑的资料请关注IT俱乐部其它相关文章!