字符串是软件开发中最为重要的对象之一。通常,字符串对象或者其等价对象,在
内存中总是占据了最大的空间块。因此如何高效地处理字符串,必将是提高系统整体性能的关键所在。
String对象及其特点
String对象是Java中重要的数据类型,它不是基础数据类型。在Java中,String对象可以认为是char数组的延伸和进一步封装。下图展示了Java中String类的基本实现,它主要由3部分组成:char数组、偏移量和String的长度。 char数组表示String的内容,它是String对象所表示的字符串的超集。String的真实内容还需要借助偏移量和长度在这个char数组中进行定位和截取。
String对象有3个基本特点:
不变性
不变性是指String对象一旦生成,则不能再对它进行改变。
针对常量池的优化
当两个String对象拥有相同的值时,它们只引用常量池中的同一个拷贝。当同一个字符串反复出现时,这个技术可以大幅度节省内存空间。
类的final定义
作为final类的String对象,在系统中不可能有任何子类,这是对系统
安全性的保护。
substring()方法的内存泄露
class="java">
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);
}
// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
this.value = value;
this.offset = offset;
this.count = count;
}
在方法最后,返回了一个新建的String对象。查看该String的
构造函数以及函数
注释可知,这是一个包作用域的构造函数,其目的是为了高效快速地共享char数组。
但在这种通过偏移量和长度来定位和截取字符串的方法中,String的原生内容value数组被整个复制到新的子字符串中。设想,如果原生字符串很大,截取的字符串长度却很短,那么截取的子字符串中却包含了整个原生字符串的所以内容,并占据了相应的内存空间,而仅仅通过偏移量和长度来定位和截取真实内容。
提高了运算速度,浪费了大量的内存空间。典型的空间换时间!
下面以一个实例来说明内存泄露的情况:
static class HugeString {
private String str = new String(new char[100000]);
public String getSubStr(int beginIndex, int endIndex) {
return str.substring(beginIndex, endIndex);
}
}
static class ImprovedHugeString {
private String str = new String(new char[100000]);
public String getSubStr(int beginIndex, int endIndex) {
return new String(str.substring(beginIndex, endIndex));
}
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 1000; i++) {
//HugeString hugeString = new HugeString();
ImprovedHugeString hugeString = new ImprovedHugeString();
list.add(hugeString.getSubStr(0, 5));
}
System.out.println("over");
}
ImprovedHugeString类的实例不会引发内存泄露的原因是因为其重新调用了没有内存泄露风险的构造函数,对需要占用的内存空间进行了重新整理。释放了substring方法返回的、存在内存泄露的String对象的强引用,以便其能被GC回收。如下所示:
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;
}