图解Java的substring()方法底层干了啥?

JDK 6中substring(int beginIndex,int endIndex)方法的实现与JDK 7中的实现是不同的。在本文中会去解释这些差异。为简单起见,我们用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被赋予x.substring(1,3)的结果时,它就会指向一个新的字符串,如下所示


然而,这个图表并不是完全正确的。在JDK6和JDK7中,那么当调用substring()时究竟发生了什么不同。

3. JDK 6中的substring()


字符串由后端的char数组支持。在JDK 6中,String类包含3个字段:char value[],int offset,int count。它们分别用于存储实际字符数组、数组的第一个索引、字符串中的字符数。


当调用substring()方法时,它会创建一个新字符串,但该字符串的值仍然指向堆中的同一个数组。两个字符串之间的区别就在于它们的计数和偏移值。


下面的代码经过了简化,只包含了解释这个问题的关键点。

//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. JDK 6中由substring()引起的问题


如果你有一个非常长的字符串,但是通过使用substring(),你每次只需要字符串中的很小的一部分。这将导致运行性能出现问题,因为你只需要一小部分,但是你保留了整个字符串。对于JDK 6,解决方案可以用下面的方法,这会使其指向真正的子字符串:


x = x.substring(x, y) + ""


5. JDK 7中的substring()



这个问题在JDK 7中得到了改进。在JDK 7中,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);
}


长按订阅更多精彩▼