java 7u6+ 的 substring() 采用深拷贝,避免内存泄漏,但频繁调用仍引发堆压力;应优先用 charat、流式读取、indexof 配合 substring 或 charbuffer 等零拷贝方案,并注意索引左闭右开及语义清晰性。

Java 中 String 的子串截取本身不复杂,但高效和内存安全需结合 JDK 版本与使用场景来设计。自 Java 7u6 起,substring() 已改为深拷贝底层字符数组(或 byte[],JDK 9+),不再共享原字符串的存储,因此不再因“引用大数组”导致内存泄漏——这是最根本的内存优化前提。
明确 substring 的行为边界(JDK 7u6+)
现代 JDK 中每次调用 substring(begin, end) 都会分配新数组,长度严格等于 end - begin。这意味着:
- 子串只占用自己实际需要的内存,不会拖住原始大字符串
- 原始大字符串只要无其他强引用,即可被 GC 回收
- 但频繁或大范围截取仍会触发大量对象分配和数组复制(O(n) 时间 + 堆压力)
避免高频/大尺寸 substring 的典型场景
以下情况容易引发性能瓶颈或内存紧张,应主动规避:
- 在 for 循环中对同一长字符串反复调用
substring(i, i+1)提取单字符 → 改用charAt(i)或预转为char[]后直接索引 - 从一个几百 MB 的日志字符串中只取末尾几 KB → 不要先
huge.substring(offset),而应改用Files.lines()流式读、或RandomAccessFile定位后按需读入小缓冲区 - 解析固定分隔符文本(如 CSV 行)→ 用
indexOf()找到所有分隔位置,再一次性substring;比逐段split()更可控、少对象
替代方案:零拷贝或更轻量的切片手段
当真正需要“视图式”切片(不复制内容)且能控制生命周期时,可考虑:
-
CharBuffer.wrap(charArray, offset, length):包装已有字符数组,subSequence()返回的是CharSequence视图,无新数组分配 -
String.valueOf(char[] data, int offset, int count):跳过 substring 校验开销,直接构造,适合已知数据合法的高性能路径 - 对超长文本,优先不加载为
String:用InputStreamReader+StringBuilder边读边处理,或MemoryMappedByteBuffer映射后按需解码
额外注意点:索引安全与语义清晰
无论用哪种方式,都需守住基本规则:
- 索引是左闭右开区间:
substring(2, 5)取第 2、3、4 位(共 3 个字符) -
beginIndex可等于length()(返回空串),但不可越界;endIndex最大为length() - 若只是提取固定模式(如 JSON 字段值),正则预编译 +
Matcher.group()比手动indexOf + substring更健壮、语义更明确
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











