Java实现字符串拆分的三种方法详解
作者:程序员越
字符串拆分,看似简单的操作,背后却隐藏着性能的玄机。今天,我们邀请三位顶尖高手同台竞技:String.split、StringUtils.splitByWholeSeparatorPreserveAllTokens、StringTokenizer。谁才是你项目中的最佳拍档?
第一章:三大高手简介
| 选手 | 门派 | 武功特点 | 登场年代 |
|---|---|---|---|
| String.split | JDK名门 | 正则心法,功能强大 | JDK 1.4 |
| StringUtils.splitByWholeSeparatorPreserveAllTokens | Apache Commons | 内外兼修,功能与性能并重 | |
| StringTokenizer | JDK元老 | 老而弥坚,简单纯粹 | JDK 1.0 |
第二章:巅峰对决——性能篇
2.1 性能基准测试
测试环境: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(",") // 编译正则,创建Pattern对象
2. Pattern.compile(...).split(str) // 执行拆分
3. 创建临时数组 // 内存分配
4. 正则引擎匹配 // 复杂的状态机遍历
JDK底层对单字符做了特殊处理,不用正则引擎,而使用indexOf,单字符性能显著提升
第三章:巅峰对决——功能篇
3.1 行为对比测试
测试1:连续分隔符的处理
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 | 全能选手 | 日常开发、需要正则 | 性能敏感 |
| 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。因为它们都是流式处理,不会一次性创建大数组。String.split会创建一个大数组,可能导致内存溢出。
结束语
字符串拆分,看似简单,实则暗藏玄机。四大高手各有所长:
- String.split:名门正派,能力全面
- StringUtils:内外兼修,平衡之道
- StringTokenizer:老而弥坚,返璞归真
记住:没有绝对的最好,只有最合适。理解了它们各自的优势和局限,你就能在任何场景下做出明智的决策。
到此这篇关于Java实现字符串拆分的三种方法详解的文章就介绍到这了,更多相关Java字符串拆分内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
