substring(start,end)在Java编程里面经常使用,没想到如果使用不当,会出现内存泄露。

要了解substring(),最好的方法便是查看源码(jdk6):

  /**
* <blockquote><pre>
* "hamburger".substring(4, 8) returns "urge"
* "smiles".substring(1, 5) returns "mile"
* </pre></blockquote>
*
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* @return the specified substring.
* @exception IndexOutOfBoundsException if the
* <code>beginIndex</code> is negative, or
* <code>endIndex</code> is larger than the length of
* this <code>String</code> object, or
* <code>beginIndex</code> is larger than
* <code>endIndex</code>.
*/
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);
}

插一句,这段substring()的源代码,为如何编写api提供了很好的一个例子,让我想起了老赵的一篇文章,对参数的判断,异常的处理,思路上有点接近。

值得注意的是,如果调用substring(i,i)的话(即beginIndex==endIndex)或者是substring(stringLength)(即是beginIndex==字符串长度),并不会抛出异常,而是会返回一个空的字符串,因为new String(offset + beginIndex , 0 , value)。

言归正传,真正创建字符串的,是一个String(int,in,char[])的构造函数,源代码如下:

 // 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;
}

Java里的字符串,其实是由三个私有变量定义:

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
{
/** The value is used for character storage. */
private final char value[]; /** The offset is the first index of the storage that is used. */
private final int offset; /** The count is the number of characters in the String. */
private final int count;
}

当为字符串分配内存时,char数组存储字符,offset=0,count=字符串长度。问题在于,由substring(start,end)调用构造函数String(int,in,char[])时,实际上是改变offset和count的位置达到取得子字符串的目的,而子字符串里的value[]数组,仍然指向原字符串。假设原字符串s有1GB,且我们需要的是s.substring(1,10)这样一段小的字符串,但由于substring()里的value[]数组仍然指向1GB的原字符串,导致原字符串无法在GC中释放,从而产生了内存泄露。

但为什么要这样设计呢?由于String是不可变的(immutable),基于这种共享同一个字符数组的设计有以下好处:

调用substring()时无需复制数组,可重用value[]数组;且substring()的运行是常数时间,非线性,性能得到提高(这也是第二段代码注释的意思:share values for speed)。

而劣势,便是可能会产生内存泄露(实际上,Oracle早有人提出这个bug:http://bugs.sun.com/view_bug.do?bug_id=4513622)。

如何避免这个问题呢?有一个变通的方案,通过一个构造函数,复制一段数组:

 /**
* Initializes a newly created {@code String} object so that it represents
* the same sequence of characters as the argument; in other words, the
* newly created string is a copy of the argument string. Unless an
* explicit copy of {@code original} is needed, use of this constructor is
* unnecessary since Strings are immutable.
*
* @param original
* A {@code 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;
} //smalStr no longer holds the value[] of 1GB
String smallStr = new String(s.substring(1,10));

上面的构造方法,重新复制了一段数组给v,然后再将v给字符串的数组,从而避免内存泄露。

在Java7里,String的实现已经改变,substring()方法的实现,由原来的共享数组变成了传统的拷贝,杜绝了内存泄露的同时也将运行时间由常数变成了线性:

 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);
}
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the character array argument. The {@code offset} argument is the
* index of the first character of the subarray and the {@code count}
* argument specifies the length of the subarray. The contents of the
* subarray are copied; subsequent modification of the character array does
* not affect the newly created string.
*
* @param value
* Array that is the source of characters
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code value} array
*/
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);
}

这个构造函数,每次都会复制数组,实现与Java6并不一样。至于哪个好哪个坏,其实很难说清楚。

据说有一种Rope的数据结构,可以更加高效地处理字符串,得好好看看。

参考:

http://javarevisited.blogspot.hk/2011/10/how-substring-in-java-works.html

http://eyalsch.wordpress.com/2009/10/27/stringleaks/

http://blog.zhaojie.me/2013/03/string-and-rope-1-string-in-dotnet-and-java.html

http://www.transylvania-jug.org/archives/5530

最新文章

  1. ajax处理缓冲问题
  2. &gt;hibernate的四种状态
  3. ajax参数设置略解
  4. this kernel requires an x86-64 CPU, but only detected an i686 CPU. unable to boot - please ues a ker
  5. 使用jquery修改css中带有!important的样式属性
  6. Sublime Text3 插件集合
  7. freeswitch编译
  8. php学习笔记4--php中的变量作用域
  9. Unity3D问题之EnhanceScollView选择角色3D循环滚动效果实现
  10. urlconnection.connect()和url.openconnection()的区别
  11. 洛谷 P1896 互不侵犯King
  12. xcode UIImage图片拉伸
  13. CentOS 6.6下JDK1.7安装与配置(Linux)经典入门详解案例
  14. 安装 mrtg
  15. eclipse点击包(package)时报错,安装hibernate后点击包报错org/eclipse/jpt/common/utility/exception/ExceptionHandler
  16. 版本管理工具Git(2)git的使用
  17. Vim在图形环境下全屏产生黑边
  18. redis key的过期时间
  19. Andorid源码 4.4 TAG
  20. java-部分精选面试题

热门文章

  1. iOS开发之网络编程--小文件下载
  2. 每日Scrum--No.5
  3. android JSON解析之JSONObject与GSON
  4. 《大话移动APP测试:Android与iOS应用测试指南》
  5. 你真的说的清楚ArrayList和LinkedList的区别吗
  6. android SQLiteOpenHelper使用示例
  7. MySQL中EXPLAIN的解释
  8. java帮助文档下载
  9. 【学习/研发】嵌入式Linux/Android开发有它就够了——迅为4412开发板
  10. virtualbox 在window10上的兼容性调整