为什么将“”附加到String保存内存?

时间:2010-01-27 14:52:06

标签: java performance string memory

我使用了包含大量数据的变量,比如String data。 我想以下列方式使用此字符串的一小部分:

this.smallpart = data.substring(12,18);

经过几个小时的调试(使用内存可视化工具)后,我发现对象字段smallpart记住了来自data的所有数据,尽管它只包含子字符串。

当我将代码更改为:

this.smallpart = data.substring(12,18)+""; 
问题解决了!现在我的应用程序现在使用很少的内存!

怎么可能?有谁能解释一下?我认为这个小部分一直在引用数据,但为什么呢?

更新 那我怎么能清除大字符串呢? data = new String(data.substring(0,100))会做什么吗?

9 个答案:

答案 0 :(得分:158)

执行以下操作:

data.substring(x, y) + ""

创建一个新的(较小的)String对象,并抛弃对substring()创建的String的引用,从而实现对此的垃圾收集。

要认识到的重要一点是substring()现有的字符串提供了一个窗口 - 或者更确切地说,是原始字符串下面的字符数组。因此它将消耗与原始String相同的内存。这在某些情况下可能是有利的,但如果你想获得一个子串并处理原始的String(如你所知),则会有问题。

有关详细信息,请查看JDK字符串源中的substring() method

编辑:要回答您的补充问题,从子字符串构造一个新的字符串将减少您的内存消耗,提供您对原始字符串的任何引用都是bin。

注意(2013年1月)。上述行为已更改in Java 7u6。不再使用flyweight模式,substring()将按预期工作。

答案 1 :(得分:27)

如果您查看substring(int, int)的来源,您会看到它返回:

new String(offset + beginIndex, endIndex - beginIndex, value);

其中value是原始char[]。所以你得到一个新的String,但相同底层char[]

执行data.substring() + ""时,您会获得一个新的字符串,其基础为char[]

实际上,您的用例是您应该使用String(String)构造函数的唯一情况:

String tiny = new String(huge.substring(12,18));

答案 2 :(得分:17)

当您使用substring时,它实际上并不创建新字符串。它仍然引用您的原始字符串,具有偏移和大小约束。

因此,要允许收集原始字符串,您需要创建一个新字符串(使用new String或您已获得的字符串)。

答案 3 :(得分:5)

在Java中,字符串是可以设置的对象,一旦创建了一个字符串,它就会保留在内存中,直到被垃圾回收器清理掉(并且这种清理不是你认为理所当然的事情)。

当你调用substring方法时,Java不会创建一个新的字符串,而只是在原始字符串中存储一系列字符。

因此,当您使用以下代码创建新字符串时:

this.smallpart = data.substring(12, 18) + ""; 

当您将结果与空字符串连接时,您实际创建了一个新字符串。 这就是原因。

答案 4 :(得分:5)

  

我认为这个小部分保留了下来   引用数据,但为什么?

因为Java字符串由char数组,起始偏移量和长度(以及缓存的hashCode)组成。像substring()这样的一些String操作会创建一个新的String对象,该对象共享原始的char数组,并且只有不同的偏移和/或长度字段。这是有效的,因为String的char数组在创建后永远不会被修改。

当许多子字符串引用相同的基本字符串而不复制重叠部分时,这可以节省内存。正如您所注意到的,在某些情况下,它可以保留不再需要的数据来进行垃圾回收。

解决此问题的“正确”方法是new String(String)构造函数,即

this.smallpart = new String(data.substring(12,18));
BTW,总体上最好的解决方案是首先避免使用非常大的字符串,并以较小的块处理任何输入,一次几KB。

答案 5 :(得分:3)

jwz in 1997记录:

  

如果你有一个巨大的字符串,拉出它的子串(),保持子串并允许更长的字符串变成垃圾(换句话说,子字符串具有更长的生命周期)巨大的底层字节字符串永远不会消失。

答案 6 :(得分:2)

总而言之,如果您从少量大字符串创建大量子字符串,那么使用

   String subtring = string.substring(5,23)

因为你只使用空间来存储大字符串,但是如果你从大字符串的丢失中提取少数几个小字符串,那么

   String substring = new String(string.substring(5,23));

会减少你的记忆力,因为不再需要时可以回收大字符串。

你打电话给new String是一个有用的提醒,你确实得到一个新的字符串,而不是对原始字符串的引用。

答案 7 :(得分:2)

首先,调用java.lang.String.substring会在原始String 上创建一个新窗口,使用偏移量和长度,而不是复制基础数组的重要部分。< /强>

如果我们仔细查看substring方法,我们会注意到字符串构造函数调用String(int, int, char[])并将整个char[]传递给代表字符串。这意味着 substring 将占用与原始字符串一样多的内存。

好的,但为什么+ ""导致需要的内存少于没有内存?

+上执行strings是通过StringBuilder.append方法调用实现的。在AbstractStringBuilder类中查看此方法的实现将告诉我们它最终使用我们真正需要的部分(arraycopy)进行substring

任何其他解决方法??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();

答案 8 :(得分:0)

追加&#34;&#34;一个字符串将有时保存内存。

让我们说我有一个巨大的字符串,其中包含一整本书,一百万个字符。

然后我创建了包含本书章节的20个字符串作为子字符串。

然后我创建包含所有段落的1000个字符串。

然后我创建包含所有句子的10,000个字符串。

然后我创建了包含所有单词的100,000个字符串。

我仍然只使用1,000,000个字符。如果你添加&#34;&#34;对于每个章节,段落,句子和单词,您使用5,000,000个字符。

当然,如果你只从整本书中提取一个单词,那么它就完全不同了,整本书可能是垃圾收集但不是因为这个单词含有对它的引用。

如果你有一百万个字符串并删除两端的标签和空格,那么它又会有所不同,比如10个调用来创建一个子字符串。 Java工作或工作的方式避免每次复制一百万个字符。有妥协,如果你知道妥协是什么,那就很好。