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

hsm*_*mit 193 java memory string performance

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

this.smallpart = data.substring(12,18);
Run Code Online (Sandbox Code Playgroud)

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

当我将代码更改为:

this.smallpart = data.substring(12,18)+""; 
Run Code Online (Sandbox Code Playgroud)

..问题解决了!现在我的应用程序现在使用很少的内存!

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

更新: 如何清除大字符串呢?data = new String(data.substring(0,100))会做什么吗?

Bri*_*new 159

执行以下操作:

data.substring(x, y) + ""
Run Code Online (Sandbox Code Playgroud)

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

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

有关详细信息,请查看JDK String源中的substring()方法.

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

注意(2013年1月).Java 7u6中的上述行为已发生变化.不再使用flyweight模式,并且substring()可以按预期工作.

  • 这是极少数情况下,`String(String)`构造函数(即String构造函数将String作为输入)很有用:`new String(data.substring(x,y))`与`附加"""`,但它使意图更清晰. (89认同)
  • 从技术上讲,这是一个实现细节.但它令人沮丧,并且吸引了很多人. (5认同)
  • 为了精确,substring使用原始字符串的`value`属性.我认为这就是保留参考的原因. (3认同)

Pas*_*ent 28

如果你看一下它的来源substring(int, int),你会看到它返回:

new String(offset + beginIndex, endIndex - beginIndex, value);
Run Code Online (Sandbox Code Playgroud)

value原来在哪里char[].所以你得到一个新的String但具有相同的底层char[].

当你这样做时data.substring() + "",你得到一个带有底层的新String char[].

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

String tiny = new String(huge.substring(12,18));
Run Code Online (Sandbox Code Playgroud)


Chr*_*ung 17

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

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


Kic*_*obo 5

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

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

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

this.smallpart = data.substring(12, 18) + ""; 
Run Code Online (Sandbox Code Playgroud)

实际上,当您将结果与空字符串连接时,您创建了一个新字符串.这就是为什么.


Mic*_*rdt 5

我认为这个小部分一直在引用数据,但为什么呢?

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

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

解决这个问题的"正确"方法是new String(String)构造函数,即

this.smallpart = new String(data.substring(12,18));
Run Code Online (Sandbox Code Playgroud)

顺便说一句,总体上最好的解决方案是首先避免使用非常大的字符串,并以较小的块处理任何输入,一次几KB.