java開發(fā)使用StringUtils.split避坑詳解
正文
在日常的 Java 開發(fā)中,由于 JDK 未能提供足夠的常用的操作類庫,通常我們會引入 Apache Commons Lang 工具庫或者 Google Guava 工具庫簡化開發(fā)過程。兩個(gè)類庫都為 java.lang
API 提供了很多實(shí)用工具,比如經(jīng)常使用的字符串操作,基本數(shù)值操作、時(shí)間操作、對象反射以及并發(fā)操作等。
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
但是,最近在使用 Apache Commons Lang 工具庫時(shí)踩了一個(gè)坑,導(dǎo)致程序出現(xiàn)了意料之外的結(jié)果。
StringUtils.split 的坑
也是因?yàn)椴攘诉@個(gè)坑,索性寫下一篇文章好好介紹下 Apache Commons Lang 工具庫中字符串操作相關(guān) API。
先說坑是什么,我們都知道 String 類中到的 split
方法可以分割字符串,比如字符串 aabbccdd
根據(jù) bc
分割的結(jié)果應(yīng)該是 aab
和 cdd
才對,這樣的結(jié)果也很容易驗(yàn)證。
String str = "aabbccdd"; for (String s : str.split("bc")) { System.out.println(s); } // 結(jié)果 aab cdd
可能是因?yàn)?String 類中的 split
方法的影響,我一直以為 StringUtils.split
的效果應(yīng)該相同,但其實(shí)完全不同,可以試著分析下面的三個(gè)方法輸出結(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
,但是實(shí)際上這個(gè)方法的運(yùn)行結(jié)果是:
// testA 輸出
aa
dd
可以看到 b
和 c
字母都不見了,只剩下了 a
和 b
,這是已經(jīng)發(fā)現(xiàn)問題了,查看源碼后發(fā)現(xiàn) StringUtils.split
方法其實(shí)是按字符進(jìn)行操作的,不會把分割字符串作為一個(gè)整體來看,返回的結(jié)果中不也會包含用于分割的字符。
驗(yàn)證代碼:
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 源碼分析
點(diǎn)開源碼一眼看下去,發(fā)現(xiàn)在方法注釋中就已經(jīng)進(jìn)行提示了:返回的字符串?dā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 分割字符串時(shí)入?yún)⒂兴膫€(gè)。
private static String[] splitWorker( final String str, // 原字符串 final String separatorChars, // 分隔符 final int max, // 分割后返回前多少個(gè)結(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. 用于截取的開始下標(biāo)置為 0 ,逐字符讀取字符串。
b. 碰到分割的目標(biāo)字符,把截取的開始下標(biāo)到當(dāng)前字符之前的字符串截取出來。
c. 然后用于截取的開始下標(biāo)置為下一個(gè)字符,等到下一次使用。
d. 繼續(xù)逐字符讀取字符串、
2. 分隔符為單個(gè)字符
邏輯同上,只是判斷邏輯 Character.isWhitespace()
變?yōu)榱酥付ㄗ址袛唷?/p>
// Optimise 1 character case final char sep = separatorChars.charAt(0); while (i < len) { if (str.charAt(i) == sep) { // 直接比較 ...
3. 分隔符為字符串
總計(jì)邏輯同上,只是判斷邏輯變?yōu)榘袛唷?/p>
// standard case while (i < len) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { // 包含判斷 if (match || preserveAllTokens) {
如何解決?
1. 使用 splitByWholeSeparator
方法。
我們想要的是按整個(gè)字符串分割,StringUtils 工具類中已經(jīng)存在具體的實(shí)現(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 方法可以實(shí)現(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避坑詳解的詳細(xì)內(nèi)容,更多關(guān)于java開發(fā)StringUtils.split避坑的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于SpringBoot實(shí)現(xiàn)動態(tài)配置數(shù)據(jù)庫的加載
這篇文章主要介紹了Spring?Boot?如何動態(tài)配置數(shù)據(jù)庫的加載,現(xiàn)項(xiàng)目有一個(gè)需求,期望通過在application.yml配置文件中設(shè)置一個(gè)開關(guān),來決定是否加載數(shù)據(jù)庫,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-10-10java 基礎(chǔ)之final、finally和finalize的區(qū)別
這篇文章主要介紹了java 基礎(chǔ)之final、finally和finalize的區(qū)別的相關(guān)資料,需要的朋友可以參考下2017-05-05鑒權(quán)認(rèn)證+aop+注解+過濾feign請求的實(shí)例
這篇文章主要介紹了鑒權(quán)認(rèn)證+aop+注解+過濾feign請求的實(shí)例講解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03JavaApi實(shí)現(xiàn)更新刪除及讀取節(jié)點(diǎn)
這篇文章主要介紹了JavaApi實(shí)現(xiàn)更新刪除及讀取節(jié)點(diǎn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05在SpringBoot項(xiàng)目中使用JetCache緩存的詳細(xì)教程
Spring Boot是一個(gè)非常流行的Java開發(fā)框架,JetCache是一個(gè)基于注解的高性能緩存框架,本文將介紹如何在Spring Boot項(xiàng)目中使用JetCache緩存,并提供一個(gè)詳細(xì)案例來說明如何配置和使用JetCache,需要的朋友可以參考下2024-06-06基于指針pointers和引用references的區(qū)別分析
本篇文章介紹了,基于指針pointers和引用references的區(qū)別分析。需要的朋友參考下2013-05-05