目录
正文StringUtils.split 的坑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避坑的资料请关注中国红客联盟其它相关文章!