java.lang.String的substring、split会导致内存溢出
下文笔者讲述String的substring方法导致OutOfMemoryError的处理方法分享
例:异常测试示例
除substring方法外
public class TestClass { private String large = new String(new char[100000]); public String getSubString() { return this.large.substring(0,2); } public static void main(String[] args) { Arraylist<String> subStrings = new ArrayList<String>(); for (int i = 0; i <1000000; i++) { TestClass testClass = new TestClass(); subStrings.add(testClass.getSubString()); } } } //运行以上程序,会报以下异常 Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
出现OutOfMemoryError异常的原因
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的一个私有的构造方法,如下: // 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; }
该方法为 避免内存拷贝,提高性能, 并没有重新创建char数组, 而直接复用原String对象的char[] 通过改变偏移量和长度来标识不同的字符串内容 substring出的来String小对象 仍然会指向原String大对象的char[] 所以就导致OutOfMemoryError问题getSubString方法变更
public String getSubString() { return new String(this.large.substring(0,2)); } 将substring的结果 重新new一个String出来 再运行该程序,则没有出现OutOfMemoryError的问题
除substring方法外
String的split方法
也存在同样的问题
split源码如下:
public String[] split(String regex, int limit) {
return Pattern.compile(regex).split(this, limit);
}
//String的split方法通过Pattern的split方法来实现
//Pattern的split方法:
public String[] split(CharSequence input, int limit) {
int index = 0;
boolean matchLimited = limit > 0;
ArrayList<String> matchList = new ArrayList<String>();
Matcher m = matcher(input);
// Add segments before each match found
while(m.find()) {
if (!matchLimited || matchList.size() < limit - 1) {
String match = input.subSequence(index, m.start()).toString();
matchList.add(match);
index = m.end();
} else if (matchList.size() == limit - 1) { // last one
String match = input.subSequence(index,
input.length()).toString();
matchList.add(match);
index = m.end();
}
}
// If no match was found, return this
if (index == 0)
return new String[] {input.toString()};
// Add remaining segment
if (!matchLimited || matchList.size() < limit)
matchList.add(input.subSequence(index, input.length()).toString());
// Construct result
int resultSize = matchList.size();
if (limit == 0)
while (resultSize > 0 && matchList.get(resultSize-1).equals(""))
resultSize--;
String[] result = new String[resultSize];
return matchList.subList(0, resultSize).toArray(result);
}
方法中
Stirng match = input.subSequence(intdex, m.start()).toString();
调用String类的subSequence方法
该方法源码如下:
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
从源码上
我们可以看出
最终调用的是String类的substring方法
因此存在同样的问题
split出来的小对象
直接使用原String对象的char[]。
StringBuilder和StringBuffer的substring方法
//则不存在这样的问题
//其源码如下
public String substring(int start, int end) {
if (start < 0)
throw new StringIndexOutOfBoundsException(start);
if (end > count)
throw new StringIndexOutOfBoundsException(end);
if (start > end)
throw new StringIndexOutOfBoundsException(end - start);
return new String(value, start, end - start);
}
最后调用String类的public构造方法,源码如下:
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.offset = 0;
this.count = count;
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
该方法不是直接使用原String对象的char[]
而重新进行了内存拷贝,所以不会出现内存溢出
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。