Java實現字符串拆分的三種方法詳解
字符串拆分,看似簡單的操作,背后卻隱藏著性能的玄機。今天,我們邀請三位頂尖高手同臺競技:String.split、StringUtils.splitByWholeSeparatorPreserveAllTokens、StringTokenizer。誰才是你項目中的最佳拍檔?
第一章:三大高手簡介
| 選手 | 門派 | 武功特點 | 登場年代 |
|---|---|---|---|
| String.split | JDK名門 | 正則心法,功能強大 | JDK 1.4 |
| StringUtils.splitByWholeSeparatorPreserveAllTokens | Apache Commons | 內外兼修,功能與性能并重 | |
| StringTokenizer | JDK元老 | 老而彌堅,簡單純粹 | JDK 1.0 |
第二章:巔峰對決——性能篇
2.1 性能基準測試
測試環(huán)境:JMH基準測試,拆分100萬次,字符串長度100,10個字段
| 選手 | 吞吐量 (ops/ms) | 相對性能 | 內存分配 |
|---|---|---|---|
| StringTokenizer | 8,200 | 96% | 極少 |
| StringUtils.splitByWholeSeparatorPreserveAllTokens | 7,800 | 92% | 中等 |
| String.split | 2,800 | 33% | 較多 |
核心結論:
- StringTokenizer:性能之王,當之無愧的冠軍
- StringUtils:雖為第三方庫,性能卻遠超JDK原生split
- String.split:慘遭墊底,僅為冠軍1/3的性能
2.2 為什么String.split這么慢?
關鍵原因:每次調用都要編譯正則表達式!
// 分解String.split的成本
str.split(",") 的實際開銷:
1. Pattern.compile(",") // 編譯正則,創(chuàng)建Pattern對象
2. Pattern.compile(...).split(str) // 執(zhí)行拆分
3. 創(chuàng)建臨時數組 // 內存分配
4. 正則引擎匹配 // 復雜的狀態(tài)機遍歷
JDK底層對單字符做了特殊處理,不用正則引擎,而使用indexOf,單字符性能顯著提升
第三章:巔峰對決——功能篇
3.1 行為對比測試
測試1:連續(xù)分隔符的處理
String str = "a,,b,c"; // 兩個逗號之間是空字符串
// String.split
System.out.println(Arrays.toString(str.split(",")));
// 輸出: [a, , b, c] ? 默認保留中間空
// StringUtils.splitByWholeSeparatorPreserveAllTokens
System.out.println(Arrays.toString(
StringUtils.splitByWholeSeparatorPreserveAllTokens(str, ",")));
// 輸出: [a, , b, c] ? 保留所有空
// StringTokenizer
StringTokenizer st = new StringTokenizer(str, ",");
while(st.hasMoreTokens()) System.out.print(st.nextToken() + " ");
// 輸出: a b c ? 空字符串被直接跳過了!
結論:StringTokenizer默認不返回空標記,這是它與眾不同的地方。
測試2:末尾分隔符的處理
String str = "a,b,c,"; // 末尾有逗號
// String.split (默認)
System.out.println(Arrays.toString(str.split(",")));
// 輸出: [a, b, c] ? 末尾空被丟棄
// String.split (保留末尾空)
System.out.println(Arrays.toString(str.split(",", -1)));
// 輸出: [a, b, c, ] ? 用limit=-1保留
// StringUtils.splitByWholeSeparatorPreserveAllTokens
System.out.println(Arrays.toString(
StringUtils.splitByWholeSeparatorPreserveAllTokens(str, ",")));
// 輸出: [a, b, c, ] ? 默認保留
// StringTokenizer
StringTokenizer st = new StringTokenizer(str, ",");
while(st.hasMoreTokens()) System.out.print(st.nextToken() + " ");
// 輸出: a b c ? 末尾空被忽略
測試3:特殊分隔符的處理
String str = "a.b.c";
// String.split (需要轉義)
System.out.println(Arrays.toString(str.split("\\.")));
// 輸出: [a, b, c] ? 但必須記得轉義
// String.split (忘了轉義)
System.out.println(Arrays.toString(str.split(".")));
// 輸出: [] ? 空數組!因為"."匹配任意字符
// StringUtils.splitByWholeSeparatorPreserveAllTokens
System.out.println(Arrays.toString(
StringUtils.splitByWholeSeparatorPreserveAllTokens(str, ".")));
// 輸出: [a, b, c] ? 不用轉義,直接使用
// StringTokenizer
StringTokenizer st = new StringTokenizer(str, ".");
while(st.hasMoreTokens()) System.out.print(st.nextToken() + " ");
// 輸出: a b c ? 也不用轉義
測試4:多字符分隔符
String str = "a--b--c";
// String.split
System.out.println(Arrays.toString(str.split("--")));
// 輸出: [a, b, c] ? 支持,但"--"是正則,特殊場景需要轉義
// StringUtils.splitByWholeSeparatorPreserveAllTokens
System.out.println(Arrays.toString(
StringUtils.splitByWholeSeparatorPreserveAllTokens(str, "--")));
// 輸出: [a, b, c] ? 原生支持多字符,且不涉及正則
// StringTokenizer
StringTokenizer st = new StringTokenizer(str, "--");
while(st.hasMoreTokens()) System.out.print(st.nextToken() + " ");
// 輸出: a b c ? 注意:這是按'-'拆分,不是按"--"!
3.2 功能對比總表
| 特性 | String.split | StringUtils.xxx | StringTokenizer |
|---|---|---|---|
| 保留中間空標記 | ? 支持 | ? 支持 | ? 不支持 |
| 保留末尾空標記 | 需limit=-1 | ? 默認保留 | ? 不支持 |
| 正則表達式支持 | ? 強大 | ? 不支持 | ? 不支持 |
| 多字符分隔符 | ? 支持(正則) | ? 原生支持 | ? 視為字符集 |
| 特殊字符轉義 | 必須轉義 | 無需轉義 | 無需轉義 |
| null輸入 | 拋NPE | 返回null | 拋NPE |
| 返回類型 | String[] | String[] | Enumeration |
第四章:終極總結
4.1 三大高手定位
| 選手 | 定位 | 適用場景 | 不適用場景 |
|---|---|---|---|
| String.split | 全能選手 | 日常開發(fā)、需要正則 | 性能敏感 |
| StringUtils.xxx | 平衡大師 | 需要保留空標記、多字符分隔符 | 正則表達式匹配 |
| StringTokenizer | 元老級 | 大文件處理、遺留系統 | api陳舊,功能匱乏,不在推薦使用 |
4.2 如何選擇
如果你不需要正則,大多數場景直接用:StringUtils
String[] result = StringUtils.splitByWholeSeparatorPreserveAllTokens(str, ",");
如果你需要正則能力:String.split 或 Pattern
String[] result = str.split("\\s+"); // 按空白符拆分
如果你僅處理很簡單的文本,追求極致性能:StringTokenizer
StringTokenizer st = new StringTokenizer(str, ",");
4.3 面試官最愛問的問題
Q1:StringUtils.splitByWholeSeparatorPreserveAllTokens為什么比String.split快?
A:因為它使用普通字符串查找(indexOf),不涉及正則表達式,避免了編譯開銷。同時它一次性分配內存,不像StringTokenizer那樣懶加載。
Q2:StringTokenizer現在還推薦使用嗎?
A:除非你在維護遺留系統,或者明確需要它"不返回空標記"的特性,否則不建議在新代碼中使用。它的API太古老,不支持現代Java特性,而且行為與主流方法不一致。
Q3:如果我要解析1GB的日志文件,該用哪個?
A:StringTokenizer或手寫indexOf。因為它們都是流式處理,不會一次性創(chuàng)建大數組。String.split會創(chuàng)建一個大數組,可能導致內存溢出。
結束語
字符串拆分,看似簡單,實則暗藏玄機。四大高手各有所長:
- String.split:名門正派,能力全面
- StringUtils:內外兼修,平衡之道
- StringTokenizer:老而彌堅,返璞歸真
記住:沒有絕對的最好,只有最合適。理解了它們各自的優(yōu)勢和局限,你就能在任何場景下做出明智的決策。
到此這篇關于Java實現字符串拆分的三種方法詳解的文章就介紹到這了,更多相關Java字符串拆分內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

