Java substring() 方法内存泄漏问题及修复
String是Java中的一个特殊类。 substring()是String类中广泛使用的方法之一。它用于提取字符串的一部分,并具有两个重载变体:
1. 子字符串(int beginIndex)
此方法用于提取从beginIndex开始的部分字符串。
例子:
Java
class GFG {
public static void main(String args[]) {
String s = "geeksforgeeks";
String subString = s.substring(4);
System.out.print(subString);
}
}
Java
class GFG {
public static void main(String args[]) {
String s = "geeksforgeeks";
String subString = s.substring(5, 13);
System.out.print(subString);
}
}
Java
//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);
}
beginIndex参数必须在 source 字符串的范围内,否则您会看到以下异常:
java.lang.StringIndexOutOfBoundsException: String index out of range:
2. substring(int beginIndex, int endIndex)
此变体接受两个参数beginIndex和endIndex 。它从beginIndex到endIndex – 1中断字符串。
例子:
Java
class GFG {
public static void main(String args[]) {
String s = "geeksforgeeks";
String subString = s.substring(5, 13);
System.out.print(subString);
}
}
substring() 如何在内部工作
我们都知道Java中的String是字符序列。 String 在内部由字符数组表示,当创建新的 String 对象时,它具有以下字段。
- char value[] –字符数组
- int count – 字符串中的总字符数
- int offset –字符数组中的起始索引偏移量
String s = “geeksforgeeks”;
value[] = {‘g’, ‘e’, ‘e’, ‘k’, ‘s’, ‘f’, ‘o’, ‘r’, ‘g’, ‘e’, ‘e’, ‘k’, ‘s’}
count = 13
offset = 0
当我们从原始字符串中获取子字符串时,将在常量池或堆中创建新的 String 对象。 value[] char 数组将在两个 String 对象之间共享,但 String 对象的 count 和 offset 属性会根据子字符串长度和起始索引而有所不同。
String s = “geeksforgeeks”;
String substr = s.substring(5, 8)
For substr:
value[] = {‘g’, ‘e’, ‘e’, ‘k’, ‘s’, ‘f’, ‘o’, ‘r’, ‘g’, ‘e’, ‘e’, ‘k’, ‘s’}
count = 3
offset = 5
JDK 6 中 substring() 引起的问题
此方法适用于小字符串。但是,当从具有更多字符的字符串中获取 substring() 时,如果您使用的是 JDK 6 或更低版本,则会导致严重的内存问题。
例子:
String bigString = new String(new byte[100000])
上面的 String 已经在堆中占用了大量内存。现在考虑我们需要来自 bigString 的前 2 个字符的场景。
String substr = bigString.substring(0, 2)
现在我们不需要原来的字符串了。
bigString = null
我们可能认为bigString对象将被垃圾收集,因为我们将其设为null ,但我们的假设是错误的。当我们调用substring()时,会在内存中创建一个新的 String 对象。但它仍然引用原始字符串中的char[]数组值。这可以防止bigString进行垃圾收集过程,并且我们在内存中不必要地存储了100000 个字节(仅用于 2 个字符) 。可以在此处找到错误详细信息。
在 JDK 6 中处理 substring()
此问题应由开发人员处理。一种选择是从子字符串返回的字符串创建新的字符串对象。
String substr = new String(bigString.substring(0, 2))
现在,在Java堆中创建了新的 String 对象,拥有自己的 char[] 数组,最终原始 bigString 将符合垃圾收集过程。
其他选项是,在子字符串上调用intern()方法,然后从池中获取现有字符串或在必要时添加它。
String substr = bigString.substring(0, 2).intern()
修复 JDK 7 中的 substring()
Sun Microsystems 从 JdK 7 更改了 substring() 的实现。当我们在 JDK 7 中调用 substring() 时,jvm 不是从原始 String 引用 char[] 数组,而是使用自己的 char[] 数组创建新的 String 对象。
Java
//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);
}
值得注意的是,在 JDK 7 中调用 substring() 方法时,会从内存中引用新的String对象,从而使原始字符串符合垃圾回收的条件。