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

Java6 String.substring()方法的内存泄露的更多相关文章

  1. C#--String.Substring方法

    第一种:String.SubString(int start,int length)    截取指定长度的字符串 这里有两个int型的参数  string表示字符串截取的起始位置,length表截取的 ...

  2. Delphi结构中使用String时遇到的内存泄露问题(没有利用String的引用计数自动销毁字符串的功能)

    先定义一个结构: TUserInfo = record  UserID: Integer; // 用户编号  UserName: string; // 用户名end; 然后编写如下代码: proced ...

  3. String中substring方法内存泄漏问题

    众所周知,JDK中以前String类中的substring方法存在内存泄漏问题,之所以说是以前,是因为JDK1.7及以后的版本已经修复了,我看都说JDK1.6的版本也存在这个问题,但是我本机上安装的1 ...

  4. Java常见问题分析(内存溢出、内存泄露、线程阻塞等)

    Java垃圾回收机制(GC) 1.1 GC机制作用 1.2 堆内存3代分布(年轻代.老年代.持久代) 1.3 GC分类 1.4 GC过程 Java应用内存问题分析 2.1 Java内存划分 2.2 J ...

  5. 强大的windbg定位内存泄露,两句命令搞定!

    1.简单配置在windbg程序目录下有个gflags.exe,运行后设置: 运行CMD.EXE,输入"D:\Debugging Tools for Windows (x86)\gflags. ...

  6. 项目问题总结:Block内存泄露 以及NSTimer使用问题

    BLock的内存泄露 在我们代码中关于block的使用可以说随处可见,第一次接触block的时候是关于UIView的块动画,那时觉得block的使用好神奇,再后来分析总结为block其实就是一个c语言 ...

  7. String.IndexOf String.IndexOf String.Substring

    String.IndexOf String.IndexOf 方法 (Char, Int32, Int32)报告指定字符在此实例中的第一个匹配项的索引.搜索从指定字符位置开始,并检查指定数量的字符位置. ...

  8. ARC下的内存泄露

    iOS提供了ARC功能,很大程度上简化了内存管理的代码. 但使用ARC并不代表了不会发生内存泄露,使用不当照样会发生内存泄露. 下面列举两种ARC导致内存泄露的情况. 1,循环参照 A有个属性参照B, ...

  9. dotnet 6 在 Win7 系统证书链错误导致 HttpWebRequest 内存泄露

    本文记录我将应用迁移到 dotnet 6 之后,在 Win7 系统上,因为使用 HttpWebRequest 访问一个本地服务,此本地服务开启 https 且证书链在此 Win7 系统上错误,导致应用 ...

随机推荐

  1. iOSQuartz2D-01-核心要点

    简介 作用 绘制 绘制图形 : 线条\三角形\矩形\圆\弧等 绘制文字 绘制\生成图片(图像) 读取\生成PDF 截图\裁剪图片 自定义UI控件(通常为内部结构较复杂的控件) UIKit中的绝大部分控 ...

  2. vs(vistual studio)项目文件名字重复问题

    今天遇到一情况,比较神奇,vs项目,我更新SVN的时候,发现竟然出现文件名字重复的现象. [caption id="" align="alignnone" wi ...

  3. AOP这些应用场景(交叉业务)

    1.统计某个方法的性能,可以在每个业务方法执行前后 记录方法执行的当前时间,执行后的时间-执行前的时间= 方法执行时间.  这样就可以在开发过程中(项目未交付给客户前)统计程序的性能. 2.安全 ,权 ...

  4. eclipse发布项目时,会自动还原server.xml和content.xml文件

    因为Tomcat的端口冲突,导致eclipse发布项目时,失败.于是到server.xml文件中修改端口,重启使用eclipse发布项目,发现依然报端口冲突的错误,其原因时,刚才对server.xml ...

  5. 读书笔记——Windows环境下32位汇编语言程序设计(5)模态对话框

    资源可以用VC之类的生成,然后拷贝出来. 例如:每一个MFC工程都有一个resource.h,没有做任何修改时,这个resource.h文件是原来自带的.当对资源进行过修改添加之类的时,新添加的资源的 ...

  6. STM32启动文件选择说明

    图1. STM32F10xxx标准外设库体系结构先说这个问题,大家都知道,我们在选择使用哪些外围的的时候,是去更改从官方模版中拷贝过来的stm32f10x_conf.h文件的27-48行,把我们要用的 ...

  7. C中signed与unsigned

    unsigned ; cout<<i * -; 问结果是多少. 第一反应:-3.不过结果似乎不是这样的,写了个程序,运行了一下,发现是:4294967293. 1)在32位机上,int型和 ...

  8. RabbitMQ 一二事(5) - 通配符模式应用

    之前的路由模式是通过key相等来匹配 而通配符,顾名思义,符合条件,则进行消息匹配发送 将路由键和某模式进行匹配.此时队列需要绑定要一个模式上. 符号“#”匹配一个或多个词,符号“*”匹配不多不少一个 ...

  9. maven - pom.xml 聚合(父)工程 基本内容演示

    企业开发中所用到的基本jar包以及插件都已在此 可以自己根据实际情况酌情增减 <project xmlns="http://maven.apache.org/POM/4.0.0&quo ...

  10. MySQL数据库学习笔记(八)----JDBC入门及简单增删改数据库的操作

    [声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/4 ...