String类源码分析
String是Java中最重要的类型之一,但并不是Java的数据基本类型。
在java 7 前其实现借助于以下三个属性:
char[]:实际内容对应的字符数组的*超集*
count: 字符数量
offset: 偏移量
这种实现在java8中修改成了以下2个属性:
char[]:实际内容对应的字符数组
length:数组内容字符数量
这种变化主要为了解决jdk6及以前String.subString()方法引发的内存浪费问题。
jdk 6上String.subString方法源码:
private final char value[];
private final int offset;
private final int count;
// JDK6,包级私有构造,共享 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);
问题: 构造函数
String(int var1, int var2, char[] var3) {
this.value = var3;
this.offset = var1;
this.count = var2;
}
解释:
String对象的char[]属性,不是实际的字符串内容,也就是内存常量池中存的内容不是实际的字符串内容。
多个String对象可共用同一块常量池,避免创建多个常量,一定程度上提高String对象的创建效率和访问效率。
但是问题是:字符串和其子串的char[]属性,同时引用同一块常量池空间,即使字符串引用被销毁,只要它的任意子串引用存在(即使该子串可能只有一个字符),常量池的空间(可能是非常大的字符集),还是不能释放。
实例实验:
import java.util.List;
import java.util.ArrayList;
public class StringTest {
public static void main(String[] args) {
List<String> list = new ArrayList();
for (int i = 0; i < 10000; i++) {
try{
//jdk 6 以下代码循环6463次会报出OutOfMemoryError
MemoryTest test = new MemoryTest();
//jdk 6 以下代码可以执行完
//ImprovedMemoryTest test = new ImprovedMemoryTest();
list.add(test.getSubstring());
}catch(Error e){
System.out.println(i);
e.printStackTrace();
break;
}
}
}
static class MemoryTest {
private String value = new String(new char[10000]);
private String getSubstring() {
return this.value.substring(1, 5);
}
}
static class ImprovedMemoryTest {
private String value = new String(new char[10000]);
private String getSubstring() {
return new String(this.value.substring(1, 5));
}
}
}
解决办法:
jdk 7上String.subString方法源码:
private final char value[];
private final int length;
// 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);