我们经常像下面这样使用String的substring(int,int)方法来截取字符串。
public static void main(String... args) {
String str = "abcdefg";
str = str.substring(1, 2);
// do something with str
}
下面来比较下JDK6和JDK7下String的substring(int,int)方法的源码。
JDK6中的源码:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > count) {
throw new StringIndexOutOfBoundsException(endIndex);
}
if (beginIndex > endIndex) {
throw new StringIndexOutOfBoundsException(endIndex - beginIndex);
}
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
}
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
这里在调用new String(int, int, char[])创建一个新字符串时会共享同一个value,改变的仅仅是offset和count;旧的char数组并没有被截取且依然被新的字符串引用,所以就导致了内存泄露。
JDK7中的源码:
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
这里在调用new String(char[], int, int)创建一个新字符串时会调用Arrays.copyOfRange(char[], int, int)方法,而Arrays的copyOfRange方法会使用System.arraycopy(Object,int ,Object,int,int)来截取和复制value,此时旧的value不再被引用,所以不会造成内存泄露。
在ProgramCreek上有网友给出了JDK6下该问题的解决方案(http://www.programcreek.com/2013/09/the-substring-method-in-jdk-6-and-jdk-7/ ):
public static void main(String... args) {
String str = "abcdefg";
str = new String(str.substring(1, 2));
// do something with str
}
这样在JDK6下就会重新截取并复制一份value了,原因请参考JDK6下String构造器String(String)的源码:
public String(String original) {
int size = original.count;
char[] originalValue = original.value;
char[] v;
if (originalValue.length > size) {
// The array representing the String is bigger than the new
// String itself. Perhaps this constructor is being called
// in order to trim the baggage, so make a copy of the array.
int off = original.offset;
v = Arrays.copyOfRange(originalValue, off, off+size);
} else {
// The array representing the String is the same
// size as the String, so no point in making a copy.
v = originalValue;
}
this.offset = 0;
this.count = size;
this.value = v;
}
此时original也会通过Arrays.copyOfRange(char[], int, int)来截取和复制value,于是便避免了内存泄露。