StringTokenizer并不比split()更节省内存,因其每次实例化都复制字符数组并维护多个索引字段,而split()在JDK8+对单字符分隔符有优化且结果可复用底层数组。

StringTokenizer 并不比 split() 更节省内存,相反,在现代 Java 中它通常更耗资源且功能受限。这个前提需要先澄清:Java 的 split() 基于正则表达式,会编译 Pattern(可能缓存),而 StringTokenizer 是一个遗留类,内部使用字符数组和状态变量,看似轻量,但实际在多数场景下并无内存优势,甚至因缺乏优化、不可复用、无法跳过空字段等导致额外开销。
为什么 StringTokenizer 并不更省内存
StringTokenizer 每次实例化都会复制原始字符串的字符数组(通过 getChars()),并维护多个 int 索引字段(currentPosition, maxPosition, delimPosition 等)。而 split() 在 JDK 8+ 对简单分隔符(如单字符)有专门优化路径,可避免正则引擎介入;且返回的 String[] 中的字符串默认共享原字符串的 char[](JDK 7u6 之后已移除永久代 substring 共享,但 split 结果仍可复用底层数组,取决于实现)。
更重要的是:StringTokenizer 不支持正则、无法忽略连续分隔符、不能返回空字段、不可重置、线程不安全且已标记为“legacy”。它不是为高性能解析设计的,而是为早期 Java(1.0)的简单语法解析服务。
真正节省内存的替代方案
若目标是解析大型文本行(如 CSV、日志行)并最小化对象分配和内存占用,应避开 String.split() 和 StringTokenizer,改用以下方式:
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
手动扫描 + subSequence()(推荐):用
CharSequence接口配合String.substring()(JDK 7u6 后无数组复制)或直接操作索引,按需提取 token,不创建中间数组。例如遍历字符串,记录起始/结束位置,用line.subSequence(start, end)得到 CharSequence 视图(零拷贝)。 - Apache Commons Text 的 StrTokenizer:支持配置是否忽略空字段、是否支持引号转义,内部基于 char[] 扫描,比 StringTokenizer 更灵活,且可复用实例。
- 自定义 CharArrayTokenizer(针对 char[] 输入):如果数据源是 char[](如从 BufferedReader.read(char[]) 获取),直接在数组上滑动指针,避免 String 构造开销。这是真正的零分配解析。
- 使用流式 CSV 解析器(如 OpenCSV 或 Univocity):它们专为大文件设计,支持逐行解析、内存映射、回调模式,避免一次性加载整行字符串或全部 token。
StringTokenizer 的典型误用与风险
很多人认为 “不用正则 = 更快更省”,但 StringTokenizer 的构造方法会立即扫描整个字符串查找所有分隔符位置(预计算),对超长行(如 1MB 日志行)会造成明显延迟;而手动扫描是惰性的,只处理到当前 token 即可暂停。
另外,StringTokenizer 返回的 nextToken() 总是新建 String 对象(调用 new String(char[], offset, count)),无法避免堆分配;而手动方式可用 String.valueOf() 或直接传 index 范围给下游处理,延迟构造。
一个轻量手动解析示例
假设按逗号分隔、不处理引号、忽略前后空格:
public static void parseLine(String line, Consumer<String> handler) {
int start = 0, end;
int len = line.length();
for (int i = 0; i <= len; i++) {
if (i == len || line.charAt(i) == ',') {
end = i;
while (start < end && Character.isWhitespace(line.charAt(start))) start++;
while (end > start && Character.isWhitespace(line.charAt(end - 1))) end--;
if (start < end) {
handler.accept(line.substring(start, end));
}
start = i + 1;
}
}
}
该方法不创建任何临时数组或集合,每个 token 仅调用一次 substring(),且可进一步优化为接受 CharBuffer 或 char[] 避免 String 输入。

















