java開發(fā)使用StringUtils.split避坑詳解
正文

在日常的 Java 開發(fā)中,由于 JDK 未能提供足夠的常用的操作類庫,通常我們會引入 Apache Commons Lang 工具庫或者 Google Guava 工具庫簡化開發(fā)過程。兩個類庫都為 java.lang API 提供了很多實用工具,比如經(jīng)常使用的字符串操作,基本數(shù)值操作、時間操作、對象反射以及并發(fā)操作等。
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
但是,最近在使用 Apache Commons Lang 工具庫時踩了一個坑,導致程序出現(xiàn)了意料之外的結(jié)果。
StringUtils.split 的坑
也是因為踩了這個坑,索性寫下一篇文章好好介紹下 Apache Commons Lang 工具庫中字符串操作相關(guān) API。
先說坑是什么,我們都知道 String 類中到的 split 方法可以分割字符串,比如字符串 aabbccdd 根據(jù) bc 分割的結(jié)果應(yīng)該是 aab 和 cdd 才對,這樣的結(jié)果也很容易驗證。
String str = "aabbccdd";
for (String s : str.split("bc")) {
System.out.println(s);
}
// 結(jié)果
aab
cdd
可能是因為 String 類中的 split 方法的影響,我一直以為 StringUtils.split 的效果應(yīng)該相同,但其實完全不同,可以試著分析下面的三個方法輸出結(jié)果是什么,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 方法的預(yù)期是 aab 和 cdd ,但是實際上這個方法的運行結(jié)果是:
// testA 輸出
aa
dd
可以看到 b 和 c 字母都不見了,只剩下了 a 和 b,這是已經(jīng)發(fā)現(xiàn)問題了,查看源碼后發(fā)現(xiàn) StringUtils.split 方法其實是按字符進行操作的,不會把分割字符串作為一個整體來看,返回的結(jié)果中不也會包含用于分割的字符。
驗證代碼:
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
輸出結(jié)果和預(yù)期的一致了。
StringUtils.split 源碼分析
點開源碼一眼看下去,發(fā)現(xiàn)在方法注釋中就已經(jīng)進行提示了:返回的字符串數(shù)組中不包含分隔符。
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....
繼續(xù)追蹤源碼,可以看到最終 split 分割字符串時入?yún)⒂兴膫€。
private static String[] splitWorker(
final String str, // 原字符串
final String separatorChars, // 分隔符
final int max, // 分割后返回前多少個結(jié)果,-1 為所有
final boolean preserveAllTokens // 暫不關(guān)注
) {
}
根據(jù)分隔符的不同又分了三種情況。
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. 繼續(xù)逐字符讀取字符串、
2. 分隔符為單個字符
邏輯同上,只是判斷邏輯 Character.isWhitespace() 變?yōu)榱酥付ㄗ址袛唷?/p>
// Optimise 1 character case
final char sep = separatorChars.charAt(0);
while (i < len) {
if (str.charAt(i) == sep) { // 直接比較
...
3. 分隔符為字符串
總計邏輯同上,只是判斷邏輯變?yōu)榘袛唷?/p>
// standard case
while (i < len) {
if (separatorChars.indexOf(str.charAt(i)) >= 0) { // 包含判斷
if (match || preserveAllTokens) {
如何解決?
1. 使用 splitByWholeSeparator 方法。
我們想要的是按整個字符串分割,StringUtils 工具類中已經(jīng)存在具體的實現(xiàn)了,使用 splitByWholeSeparator 方法。
String str = "aabbccdd";
String[] resultArray = StringUtils.splitByWholeSeparator(str, "bc");
for (String s : resultArray) {
System.out.println(s);
}
// 輸出
aab
cdd
2. 使用 Google Guava 工具庫
關(guān)于 Guava 工具庫的使用,之前也寫過一篇文章,可以參考:Guava - 拯救垃圾代碼
String str = "aabbccdd";
Iterable<String> iterable = Splitter.on("bc")
.omitEmptyStrings() // 忽略空值
.trimResults() // 過濾結(jié)果中的空白
.split(str);
iterable.forEach(System.out::println);
// 輸出
aab
cdd
3. JDK String.split 方法
使用 String 中的 split 方法可以實現(xiàn)想要效果。
String str = "aabbccdd";
String[] res = str.split("bc");
for (String re : res) {
System.out.println(re);
}
// 輸出
aab
cdd
但是 String 的 split 方法也有一些坑,比如下面的輸出結(jié)果。
String str = ",a,,b,";
String[] splitArr = str.split(",");
Arrays.stream(splitArr).forEach(System.out::println);
// 輸出
a
b
開頭的逗號,前出現(xiàn)了空格,末尾的逗號,后卻沒有空格。
一如既往,文章中代碼存放在 Github.com/niumoo/javaNotes.
以上就是java開發(fā)使用StringUtils.split避坑詳解的詳細內(nèi)容,更多關(guān)于java開發(fā)StringUtils.split避坑的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理)
這篇文章主要介紹了Spring之AOP兩種代理機制對比分析(JDK和CGLib動態(tài)代理),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-05-05
Java字符串格式化功能?String.format用法詳解
String類的format()方法用于創(chuàng)建格式化的字符串以及連接多個字符串對象,熟悉C語言的同學應(yīng)該記得C語言的sprintf()方法,兩者有類似之處,format()方法有兩種重載形式2024-09-09
Spring之兩種任務(wù)調(diào)度Scheduled和Async詳解
這篇文章主要介紹了Spring之兩種任務(wù)調(diào)度Scheduled和Async,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
reactor-logback的AsyncAppender執(zhí)行流程源碼解讀
這篇文章主要為大家介紹了reactor-logback的AsyncAppender執(zhí)行流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-12-12

