【问题标题】:Why does appending "" to a String save memory?为什么将“”附加到字符串可以节省内存?
【发布时间】:2011-01-10 00:08:21
【问题描述】:

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

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

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

当我把代码改成:

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

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

这怎么可能?谁能解释一下?我认为 this.smallpart 一直在引用数据,但为什么呢?

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

【问题讨论】:

  • 在下面阅读更多关于你的最终意图:大字符串首先来自哪里?如果从文件或数据库中读取 CLOB 或其他内容,那么在解析时只读取您需要的内容将是最佳的。
  • 太棒了...我在 Java 领域工作超过 4 到 5 年,这对我来说仍然是新的 :)。谢谢兄弟的信息。
  • 使用new String(String)有一个微妙之处;见stackoverflow.com/a/390854/8946

标签: java performance string memory


【解决方案1】:

将“”附加到字符串有时会节省内存。

假设我有一个包含整本书、一百万个字符的巨大字符串。

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

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

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

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

我仍然只使用 1,000,000 个字符。如果在每个章节、段落、句子和单词中添加“”,则使用 5,000,000 个字符。

当然,如果您只从整本书中提取一个单词,则完全不同,整本书可能会被垃圾回收,但这并不是因为那个单词包含对它的引用。

如果您有一百万个字符串并在两端删除制表符和空格,则再次不同,例如调用 10 次来创建子字符串。 Java 的工作方式避免了每次复制一百万个字符。有妥协,如果你知道妥协是什么,那就太好了。

【讨论】:

    【解决方案2】:

    执行以下操作:

    data.substring(x, y) + ""
    

    创建一个新的(较小的)String 对象,并丢弃对由 substring() 创建的 String 的引用,从而启用对此的垃圾回收。

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

    查看 JDK 字符串源中的 substring() method 了解更多信息。

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

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

    【讨论】:

    • 这是String(String) 构造函数(即,将字符串作为输入的字符串构造函数)有用的极少数情况之一:new String(data.substring(x, y)) 与附加"" 的作用相同,但是它使意图更加清晰。
    • 准确地说,子字符串使用原始字符串的value 属性。我认为这就是保留引用的原因。
    • @Bishiboosh - 是的,没错。我不想暴露实现的特殊性,但这正是正在发生的事情。
    • 从技术上讲,这是一个实现细节。但这仍然令人沮丧,并且吸引了很多人。
    • 我想知道是否可以在 JDK 中使用弱引用等进行优化。如果我是最后一个需要这个 char [] 的人,而且我只需要一点点,那就创建一个新数组供我在内部使用。
    【解决方案3】:

    首先,调用java.lang.String.substring在原始String上创建新窗口,使用偏移量和长度而不是复制底层数组的重要部分。

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

    好的,但是为什么+ "" 导致对内存的需求比没有它时更少?

    strings 上执行+ 是通过StringBuilder.append 方法调用实现的。看看这个方法在AbstractStringBuilder 类中的实现会告诉我们,它终于用我们真正需要的部分(substring)完成了arraycopy

    还有其他解决方法吗?

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

    【讨论】:

      【解决方案4】:

      在 Java 中,字符串是不可变的对象,一旦创建了字符串,它就会一直保留在内存中,直到被垃圾收集器清理(而这种清理不是你可以想当然的事情)。

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

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

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

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

      【讨论】:

        【解决方案5】:

        总结一下,如果你从少量大字符串中创建大量子字符串,那么使用

           String subtring = string.substring(5,23)
        

        由于您只使用空间来存储大字符串,但是如果您要从丢失的大字符串中提取少量小字符串,那么

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

        将减少您的内存使用,因为不再需要时可以回收大字符串。

        您调用new String 是一个有用的提醒,您确实得到了一个新字符串,而不是对原始字符串的引用。

        【讨论】:

        【解决方案6】:

        jwz in 1997 记录:

        如果你有一个巨大的字符串,拉出它的一个 substring(),抓住这个子字符串,让更长的字符串变成垃圾(换句话说,子字符串有更长的生命周期)巨大的底层字节字符串永远不会消失。

        【讨论】:

          【解决方案7】:

          我认为 this.smallpart 保留 引用数据,但为什么?

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

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

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

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

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

          【讨论】:

          【解决方案8】:

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

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

          其中value 是原始char[]。所以你得到了一个新的字符串,但具有 same 底层char[]

          当你这样做时,data.substring() + "",你会得到一个带有 new 底层char[] 的新字符串。

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

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

          【讨论】:

          【解决方案9】:

          当您使用substring 时,它实际上并没有创建一个新字符串。它仍然引用您的原始字符串,带有偏移量和大小限制。

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

          【讨论】:

            猜你喜欢
            • 2021-05-01
            • 2011-03-22
            • 2021-07-03
            • 2017-12-10
            • 1970-01-01
            • 2014-10-07
            • 2014-10-10
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多