JDK6和JDK7中的substring()方法
编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。
在 JDK 6 与 JDK 7 这两个版本中,substring(int beginIndex, int endIndex) 方法的底层实现存在显著差异。了解这两个版本间的区别,有助于开发者更好地理解字符串内存管理机制。为简便起见,下文统一以 substring() 指代 substring(int beginIndex, int endIndex) 方法。
1. substring() 方法简介
String 对象的 substring(int beginIndex, int endIndex) 方法返回此对象的一个子串,范围从 beginIndex 开始,一直到 endIndex - 1 结束,共包含 (endIndex - beginIndex) 个字符。
新手提示:
String的索引与数组一样,都是从0开始。- 注意方法名字是
substring(),全小写。 - 存在一个重载方法
substring(int beginIndex),表示从beginIndex索引处开始直到字符串末尾。
示例代码:
String x = "abcdef";
int begin = 1;
int end = 3;
x = x.substring(begin, end);
System.out.println(x);执行结果(包含索引为 begin 直到 end - 1 的字符):
bc2. substring() 调用机制
众所周知,String 是不可变的(Immutable)。当执行 x = x.substring(begin, end) 时,实际上 x 指向了一个全新的字符串对象,如下图所示:
图 1
然而,这幅图并未完全揭示堆内存中真正发生的情况。那么,在 JDK 6 和 JDK 7 之间,substring() 的调用到底有哪些区别呢?
3. JDK 6 中的实现细节
在 JDK 6 中,String 实际上是一个字符数组的封装。String 对象主要包含 3 个属性域:
private final char value[];
private final int offset;
private final int count;它们分别用于存储实际的字符数组、数组的起始索引偏移量,以及 String 的字符个数。
当调用 substring() 方法时,虽然创建了一个新的 String 对象,但新对象的 value[] 属性域仍然指向堆内存中原来的那个字符数组。区别仅在于两个对象的 count 和 offset 值不同。如下图所示:
图 2
为解释这个问题,下面是最关键部分的源码:
// JDK 6, 包级私有构造,共享 value 数组以提升速度
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
// ... 检查边界的代码
// 如果范围和自己一模一样,则返回自身,否则用 value 字符数组构造一个新的对象
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}4. JDK 6 中的内存隐患
如果有一个非常长的字符串,但每次使用 substring() 时只想要很小的一部分,那么在 JDK 6 中将会引起性能问题:虽然你只需要很小的一部分字符,但新字符串对象持有了整个原始 value[] 的引用,从而导致大量内存被占用无法释放。
要解决这个问题,在 JDK 6 中可以让其指向一个真正的子字符串(强制复制数组),示例代码:
x = x.substring(begin, end) + "";5. JDK 7 中的改进
在 JDK 7 中,这个问题得到了改进。substring() 方法真实地在堆内存中创建了另一个字符数组,不再共享底层数组。
图 3
相关源码如下:
// JDK 7, 权限变为 public
public String(char value[], int offset, int count) {
// ... 检查边界..
// value 数组拷贝
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
// ... 检查边界..
int subLen = endIndex - beginIndex;
// 如果和自身一样,那就返回自身,否则返回构造的新对象
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}参考资料
相关阅读
- Top 10 questions about Java String
- Java method for spliting a camelcase string
- Java: Convert File to Char Array
- Count Number of Statements in a Java Method By Using Eclipse JDT ASTParser
原文链接:The substring() Method in JDK 6 and JDK 7
说明:本文主要对比 JDK 6 与 JDK 7 早期版本的实现差异。自 JDK 7 Update 6 起,substring() 行为已改为复制数组(同 JDK 7 描述),后续版本(JDK 8+)均沿用此机制。JDK 6 已停止维护,生产环境建议使用受支持的 LTS 版本。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/jdk6-he-jdk7-zhong-de-substring-fang-fa.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。