我使用了包含大量数据的变量,比如String data
。
我想以下列方式使用此字符串的一小部分:
this.smallpart = data.substring(12,18);
经过几个小时的调试(使用内存可视化工具)后,我发现对象字段smallpart
记住了来自data
的所有数据,尽管它只包含子字符串。
当我将代码更改为:
this.smallpart = data.substring(12,18)+"";
问题解决了!现在我的应用程序现在使用很少的内存!
怎么可能?有谁能解释一下?我认为这个小部分一直在引用数据,但为什么呢?
更新 那我怎么能清除大字符串呢? data = new String(data.substring(0,100))会做什么吗?
答案 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工作或工作的方式避免每次复制一百万个字符。有妥协,如果你知道妥协是什么,那就很好。