乐趣区

关于java:使用-StringUtilssplit-的坑

点赞 再看,能源有限。微信搜「程序猿阿朗」。

本文 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 宰割的后果应该是 aabcdd 才对,这样的后果也很容易验证。

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 办法的预期是 aabcdd,然而实际上这个办法的运行后果是:

// testA 输入
aa
dd

能够看到 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 输入
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。

退出移动版