string分析1000次循环string.substringg用了多少内存

substring()或者substr()是大部分主流语言所支持的,同时对字符串的操作是编程中最频繁的操作,当然对截取字符串也是最常用操作之一。所以今天就讨论一下subtring()。

以JAVA举例,在JAVA中我们看看系统提供的substring()有什么问题?

我们在进这个String构造方法,看看它做了什么?

我们发现惊奇的发现JDK源码中很少有对一个函数进行单行注释,这里居然出现了,肯定是作者尤其强调的,用博主“拙劣”的英文水平帮着翻译下它的意思:私有构造器共享着value数组为了去提高效率。接下来我们看看这个String构造函数,我们发现它在截取字符串的时候创造一个String对象但是引用了原来的char value[],利用偏移量的方式达到一个截取的效果,我们并不需要原来的字符串了,但JVM通过扫描发现原来的String的对象还有引用指向它,所以不能回收。这就造成了内存泄漏。

PS:JDK7版本已解决上诉问题,感兴趣的朋友可以去看源码。

所以当对内存要求高的时候,或者需要提高软件寿命,我们需要对它进行处理,博主在这里提供两种方法:

方式一:既然截取后的字符串引用指向原来的对象,我们当然可以让这个子字符串自己独占一个对象,原来的字符串就可以被回收了。

方式二:我们大可自己写一个substring()函数

有的语言比如像PHP,它的截取字符串函数substr()函数是按字节截取的,我们知道在不同编码下,一个汉字所占 字节数是不同的,所以用它截取中文可能会产生乱码,所以这种情况下用字符截取方便的多(PHP中iconv_substr()是按字符截取的),但也有很多情况需要用到字节截取,但我们知道JAVA的字符串是按字符截取的,它并没有字节截取的函数,所以我们有时得自己写按字节截取的substring()


  转载请保留原文地址

字符串是软件开发中最为重要的对象之一。而且它在内存中占据了很大的空间块。因此如何高效的处理字符串,必将是提高系统整体性能的关键。

String对象是Java语言中重要的数据类型,但它并不是Java的基本数据类型,在Java语言中,String对象可以认为是char数组的延伸和进一步封装。它主要有3部分组成:char数组,偏移量和string的长度。char数组表示String的内容,它是String对象所表示字符串的超集。String的真实内容还需要偏移量和长度在这个char数组表示Stirng内存,它是String对象所表示字符串的超集。String的真实内容还需要由偏移量和长度在这个char数组中进行定位和截取。

在Java语言中,Java的设计者对String对象进行了大量的优化,其主要表现在三个方面

这里再添加一点就是无论StringBuilder或者StringBuffer,在初始化时都可以设置一个容量参数,在不指定容量参数时,默认是16个字节。此参数指定了他们的初始化大小,在追加字符串时,如果超过实际char数组长度,则需要进行扩容。如果能够预先评估StringBuilder的大小,将能够有效的节省这些操作,从而提高系统性能。

在Java中我们无须关心内存的释放,JVM提供了内存管理机制,有垃圾回收器帮助回收不需要的对象。但实际中一些不当的使用仍然会导致一系列的内存问题,常见的就是内存泄漏和内存溢出

内存溢出(out of memory ) :通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出。

内存泄漏(leak of memory) :是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。

由substring方法引发的内存泄漏

substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全不同的(虽然它们都达到了同样的效果)。了解它们实现细节上的差异,能够更好的帮助你使用它们,因为在JDK1.6中不当使用substring会导致严重的内存泄漏问题。

String类是不可变变,当上述第二句中x被重新赋值的时候,它会指向一个新的字符串对象,就像下面的这幅图所示:

然而,这幅图并没有准确说明的或者代表堆中发生的实际情况,当substring被调用的时候真正发生的才是这两者的差别。

count,分别用来存储真实的字符数组,数组的起始位置,String的字符数。由这3个变量就可以决定一个字符串。当substring方法被调用的时候,它会创建一个新的字符串,但是上述的char数组value仍然会使用原来父数组的那个value父数组和子数组的唯一差别就是count和offset的值不一样,下面这张图可以很形象的说明上述过程。

由此引发的内存泄漏泄漏情况: null;这段简单的程序有两个字符串变量str、sub。sub字符串是由父字符串str截取得到的,假如上述这段程序在JDK1.6中运行,我们知道数组的内存空间分配是在堆上进行的,那么sub和str的内部char数组value是公用了同一个,也就是上述有字符a~字符t组成的char数组,str和sub唯一的差别就是在数组中其实beginIndex和字符长度count的不同。在第三句,我们使str引用为空,本意是释放str占用的空间,但是这个时候,GC是无法回收这个大的char数组的,因为还在被sub字符串内部引用着,虽然sub只截取这个大数组的一小部分。当str是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法: str = null;利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在JDK7中,substring 被重新实现了。

在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。下面的这张图说明了JDK7中substring的实现过程:

}可以发现是去为子字符串创建了一个新的char数组去存储子字符串中的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。

我要回帖

更多关于 string.substring 的文章

 

随机推荐