【问题标题】:StringBuilder - Reset or create a newStringBuilder - 重置或创建一个新的
【发布时间】:2013-09-16 23:43:06
【问题描述】:

我有一个条件,即 StringBuilder 不断存储与大型平面文件(100 MB)中的模式匹配的行。但是,在达到条件后,我将 StringBuilder 变量的内容写入文本文件。

现在我想知道我是否应该通过重置对象来使用相同的变量 ->

stringBuilder.delete(0,stringBuilder.length())

stringBuilder=new StringBuilder();

请就性能和OOM问题提出您认为哪个更好。

【问题讨论】:

  • 作为参考,delete() 调用执行 System.arraycopy
  • 您应该根据您的应用程序工作流程进行基准测试,并选择最适合您的。
  • @SotiriosDelimanolis 虽然这个例子的arraycopy是长度为0的。
  • 我会分析这两个选项来衡量内存和 CPU 使用的性能,并针对特定情况获得明确的答案。尽管如此,IMO 最好的选择还是 stringBuilder=new StringBuilder();,让 GC 完成它的工作。

标签: java stringbuilder


【解决方案1】:

我认为StringBuilder#delete(start, end) 仍然是昂贵的电话,你应该这样做:

stringBuilder.setLength(0);

重置它。


更新:在查看source code of StringBuilder 之后,似乎setLength(int) 保持旧缓冲区完好无损,最好在上述调用后调用:StringBuilder#trimToSize() >attempts to reduce storage used for the character sequence.

所以这样的事情会更有效:

stringBuilder.setLength(0); // set length of buffer to 0
stringBuilder.trimToSize(); // trim the underlying buffer

【讨论】:

  • 您可能会使用这种技术泄漏内存。如果您创建越来越大的字符串,然后创建更小的字符串,即使您没有全部使用它,底层char[] 也会变得更大。
  • @anubhava 我不是在谈论性能。 setLength() 方法只是将 count 字段设置为新长度。因此,如果您的 char[] 的大小为 10_000_000,那么您将毫无意义地使用所有这些空间。
  • @anubhava 假设您之前使用的 count 为 100,可能还有一个 char[100] 与之配套。调用setLength(0),其中100 > 0,即。没有\0,只有在容量增加时才会发生。而是执行 else 并将 count 设置为 0。因此,您的 char[] 引用不会被重新分配,您会留下一个 char[100],您可能无法完全使用它。即使它确实将所有字符索引设置为\0,您仍然可能会浪费它们。
  • @anubhava trimToSize() 也很危险,因为它会创建一个 char[0],必须在第一个 append() 处扩展。
  • @SotiriosDelimanolis 我知道这很古老,但是一个更大的未使用的底层数组,其中长度已设置为零与内存泄漏相同吗?我的意思是,它会用完内存,但是当 stringBuilder 超出范围时,它最终不会被垃圾收集吗?或者,将变量重新分配给新的 stringBuilder 可能会创建大量要收集的已释放垃圾。哪个更糟?我想这取决于应用程序的上下文。
【解决方案2】:

恕我直言,我建议使用 new:

stringBuilder = new StringBuilder();

我从未听说过 StringBuilder 中的内存泄漏,但是当您真正突破极限时,您永远不知道。我每次都会使用一个新实例来对冲我的赌注。

在最坏的情况下,你可能会失去一些效率并且 gc 得到锻炼,但你排除了 OOM 的可能性。

正因为如此,而且为了清楚起见,我个人会采用新方法。

【讨论】:

    【解决方案3】:

    一个根本的区别是 sb.delete 保留引用,而构造函数丢失它。

    如果您的 SB 是一个方法参数,并且应该用于将内容传递回调用者,则必须使用 sb.delete。调用者持有原始引用。

    【讨论】:

      【解决方案4】:

      这两者之间的差异更大。第一个保留删除字符之前的任何容量(即stringBuilder.capacity()),而第二个创建一个新的StringBuilder,默认容量为16。当然,您可以将stringBuilder.capacity()作为参数传递给构造函数,但是,重要的是要理解这里的区别。

      在任何情况下,我都非常怀疑这两个变体之间的性能差异是否很大,因此请选择更易读且更易于管理的那个。 只有当你最终确定这会导致某种瓶颈时,你才应该改变你的方法。

      【讨论】:

        【解决方案5】:

        重用创建的对象总是比分配一个新对象便宜。它还避免了垃圾收集器的额外工作,因为您只处理一个对象。

        更快的方法是:

        stringBuilder.setLength(0);
        

        【讨论】:

          【解决方案6】:

          我会使用:

           stringBuilder = new StringBuilder();
          

          因为如果您用大量数据填充它,调用 stringBuilder.setLength(0); 不会取消分配后备数组,因此您可能会看到内存使用率保持在不必要的高位。

          而且,它更易于阅读和理解。

          【讨论】:

          • 好吧,如果你打算很快再次使用它的全部/大部分,可以说释放和重新分配它是不必要的。
          【解决方案7】:

          理想情况下我们应该使用new StringBuilder()grepcode 挖一点 StringBuilder 类我了解了以下内容。

          创建新对象:

          /**
               * Creates an AbstractStringBuilder of the specified capacity.
               */
              AbstractStringBuilder(int capacity) {
                  value = new char[capacity];
              }
          

          new StringBuilder() 创建一个具有初始容量 char 数组的新对象。 这里的开销:将调用 GC 来清除旧对象。

          使用删除:

          public AbstractStringBuilder delete(int start, int end) {
                  if (start < 0)
                      throw new StringIndexOutOfBoundsException(start);
                  if (end > count)
                      end = count;
                  if (start > end)
                      throw new StringIndexOutOfBoundsException();
                  int len = end - start;
                  if (len > 0) {
                      System.arraycopy(value, start+len, value, start, count-end);
                      count -= len;
                  }
                  return this;
              }
          

          使用 Length 和 TrimToSize :

          public void trimToSize() {
                  if (count < value.length) {
                      value = Arrays.copyOf(value, count);
                  }
              }
          

          会从数组类调用copyOf

          public static char[] copyOf(char[] original, int newLength) { char[] 复制 = 新 char[newLength]; System.arraycopy(原始, 0, 复制, 0, Math.min(original.length, newLength)); 返回副本; }

          现在它还将调用 System.arrayCopy 这是一个本地方法。 现在,如果您在 copyOf 中看到我们再次创建一个长度为 0 的新 charArray, 而当我们尝试再次向其添加数据时,它会调用expand,因为当前长度将为0。 所以我认为最好调用 new StringBuilder()

          上面的代码可以在grepcode看到

          PS : @user3241961 是写的,以防你使用这个对象的引用,然后 new 需要重新设置它

          【讨论】:

            【解决方案8】:

            如果您处于紧密循环中,并且在将数据写入文件后将继续返回该循环,则绝对应该重新使用 StringBuilder。没有理由不这样做,这比搅动 GC 更好。如果你是用 C 或 C++ 编写的,你会重复使用缓冲区。

            另外,虽然 delete(...) 方法调用 System.arraycopy 确实如此,但复制的字节数为 0,因此无关紧要。

            啊 - 其他人提到我有一个 setLength(...) 方法,这是重新使用缓冲区的最快方法。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-01
              • 2020-05-11
              • 2015-03-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多