JDK6和JDK7中的substring(int beginIndex, int endIndex)的实现方式是有区别的,知道了这个区别之后可以帮助你更好地在程序中使用substring方法。
为简单起见,以下使用substring()来代替substring(int beginIndex, int endIndex)
1. substring()是干嘛用的?
方法substring(int beginIndex, int endIndex)返回一个以beginIndex开始,(endIndex-1)为止的子串,如下代码:
String x = "abcdef";
x = x.substring(1,3);
System.out.println(x);
输出结果是bc
2. substring()方法被调用时发生了什么?
你也许知道字符串是不可变的,所以当x被赋予了x.substring(1, 3)时,它指向了一个新的字符串,如下图所示:
但是这个图并不准确或者说这并不能代表在堆中实际发生的事情,该方法的调用在JDK7和JDK6中的实现是完全不同的。
3. JDK6中的substring()
String是通过char数组来实现的。在JDK6中String类包含了3个域:char value[], int offset, int count,分别表示存储的字符数组,该数组的起始索引,以及该字符串中的字符个数。
当方法substring()方法被调用时,它便生成了一个新的字符串,但是该字符串仍旧指向堆中的同一个字符数组,区别就在于偏移量offset和字符个数count的变化,请看下图:
以下简化的代码包含了重要的关键点来解释这个问题:
//JDK 6
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
public String substring(int beginIndex, int endIndex) {
//check boundary
return new String(offset + beginIndex, endIndex - beginIndex, value);
}
4. JDK6中的substring()方法引起的问题
如果你有一个超级长的字符串,但实际上你每次调用substring()方法只使用其中的一小段,这就会引起性能问题,也就是你只用了一点点,却还是要保存整个超长字符串,利用率实在太低了。在JDK6中可以使用以下方法使得子字符串转化为一个真实的字符串:
x = x.substring(a, b) + ""
5. JDK7中的substring()
这个问题终于在JDK7中得以改进。在JDK7中,substring()方法的调用确实是在堆中生成了新的字符串,如下图所示:
以下也是简化的代码,包含了重要的关键点来解释这一点:
//JDK 7
public String(char value[], int offset, int count) {
//check boundary
this.value = Arrays.copyOfRange(value, offset, offset + count);
}
public String substring(int beginIndex, int endIndex) {
//check boundary
int subLen = endIndex - beginIndex;
return new String(value, beginIndex, subLen);
}