如何清空一个StringBuilder?

748
我在循环中使用了一个 StringBuilder,每 x 次迭代之后,我想要清空并以一个空的 StringBuilder 开始,但是在文档中我没有看到类似于 .NET 的StringBuilder.Clear方法,只有delete方法,看起来过于复杂。

那么,在Java中清空一个 StringBuilder 的最佳方式是什么?

9个回答

957

两种可行的方法:

  1. 使用 stringBuilderObj.setLength(0)
  2. 使用 new StringBuilder() 来分配一个新的对象,而不是清空缓冲区。注意,在对性能非常关键的代码路径进行优化时,这种方法可能比基于 setLength 的方法慢得多(因为需要分配一个带有新缓冲区的新对象,旧对象变得可以被GC回收等)。

342
不,它并不便宜!你怎么能这样说呢?假设你有一个容量为1000个字符的缓冲区。然后你将其处理掉(由GC负责),再创建一个新的(由分配器负责)。相比之下,仅仅将文本长度设为零(对CPU几乎没有工作量),重复使用同一个缓冲区会更快。 - Sulthan
15
@Sulthan: 哦,对不起回答晚了:我在考虑StringBuffer.delete(idx, len)。另一方面,使用setLength需要迭代整个缓冲区并将每个字符设置为null(例如http://kickjava.com/src/java/lang/AbstractStringBuilder.java.htm)。根据缓冲区的大小,那可能也很昂贵。另一方面,除非它是超级高效的代码,否则使用看起来最清晰的方法,不要花时间进行微调优化。 - Marcus Frödin
91
@Marcus,你提供的链接中,setLength(0)不会像你所说的那样进行迭代,只有在新长度大于已使用字符数时才会这样做(对于0长度是不可能发生的)。从性能上看,setLength(0)似乎是最好的选择,同时也表明了清空缓冲区的非常清晰的含义。 - Eran
20
@Marcus 你应该更新你的答案。 - Rag
7
仔细阅读源代码:if (count < newLength),但如果 newLength 为0,这种情况永远不会发生。 - biziclop
显示剩余20条评论

315

基本上有两种选择,使用 setLength(0) 重置 StringBuilder 或在每次迭代中创建一个新的 StringBuilder。根据使用情况,两者都有利弊。

如果您预先知道 StringBuilder 的期望容量,则每次创建一个新的 StringBuilder 应该与设置新长度一样快。这也有助于垃圾收集器,因为每个 StringBuilder 的生命周期相对较短,垃圾收集器也针对此进行了优化。

当您不知道容量时,复用同一个 StringBuilder 可能更快。每次添加时超过容量后,都必须分配一个新的后备数组并复制以前的内容。通过重用同一个 StringBuilder,在一些迭代后它将达到所需的容量,之后就不会有任何复制了。


1
谢谢,我忘记了带有容量参数的构造函数。 - Hans Olsson
如果您使用setLength(0),那么这是否意味着它保持内部缓冲区的当前长度?我的担心是,我不想“new”一个新的StringBuffer,因为我期望有时会有相当长的字符串,因此我从一个相当大的缓冲区大小开始(4k或32k)。因此,似乎设置setLength(0)可能更快。但是-如果StringBuffer分配的空间从不缩小,我可能会耗尽内存(这在Android下可能会出现内存紧张的情况)。 - Michael
1
@Michael:是的,内部缓冲区保持当前长度。你可以在https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/java/lang/AbstractStringBuilder.java找到Android的实际实现。一旦你完成了字符的追加,你可以使用`trimToSize`方法来释放不必要的空间。 - Jörn Horstmann
你写道:“根据使用情况,两者都有优缺点。” 你能举例说明在每次迭代中创建新的 StringBuilder 更好的情况吗? - icza
1
@icza 举个例子,如果你想要并行处理。 - biziclop
新的 StringBuilder 具有更好的可维护性优势。重复使用变量意味着它将分散在整个代码中。通常情况下,变量应该局部化到最紧密的范围内,这样可以使代码更简单、更易于理解。这听起来可能很抽象,但当你编写一些需要长时间被许多人阅读和维护的代码时,这是非常重要的。 - Vsevolod Golovanov

84

delete并不是特别复杂:

myStringBuilder.delete(0, myStringBuilder.length());

你也可以这样做:

myStringBuilder.setLength(0);

1
复杂可能不是正确的词,我指的更多是它看起来不够整洁。 - Hans Olsson
4
这就是为什么我添加了setLength(0)版本,它应该更快。但是新的分配可能会更快。 - krtek
4
“setLength” 的替代方案很有意思,谢谢。 - Hans Olsson
3
假设一个StringBuilder对象被作为输出参数传递给一个函数,那么新分配内存不是一个选项。 - Mubashar
@MubasharAhmad 你不能在参数中使用 new 吗?例如 function(new StringBuilder()) 然后每个函数调用都会创建一个新的分配,不是吗? - Davos
显示剩余2条评论

33

如果您查看StringBuilder或StringBuffer的源代码,setLength()方法只是重置了字符数组的索引值。在我看来,使用setLength()方法总是比进行新的分配更快。他们应该将这个方法命名为“清除”或“重置”,这样就更清晰明了。


4
只有当你在扩展字符串时才需要这样做。如果你要缩小它,Javamann是正确的。 - adam.r
@FrankHarper,你错了。当newLength为零时,源代码不执行任何操作。 - mjs
同时,setLength也会导致内存泄漏,但你会发现这一点太晚了。有时候SO的人会给出非常愚蠢的答案。setLength除了将长度设置为零之外什么也不做。其余的分配仍然存在。这个答案源于javascript数组的length = 0,它执行一个神奇的操作来标记数组可重用,但即使在那里我也不确定,并且不信任它。底层数组永远不会被垃圾回收。 - mjs

21

我会选择 sb.setLength(0); 不仅因为它只需要一次函数调用,而且它不像 sb.delete(0, builder.length()); 那样将数组复制到另一个数组中。 它只是填充剩余的字符为 0 并将长度变量设置为新的长度。

您可以查看他们的实现来验证我的观点,从此处setLength 函数和 delete0 函数。


3
不要挑字眼,只需阅读答案以了解我的观点。 - Ahmed Hegazy
2
setLength也会导致内存泄漏,但你会发现这个问题太晚了。setLength除了将长度设置为零之外什么都不做。其余的分配仍然存在。 - mjs
1
@momomo 好处是你可以重复使用它,而不必创建新的数组,从而避免了不必要的GC触发。当你使用完StringBuilder后,它会被全部垃圾回收掉。 - Ahmed Hegazy

10

6

我认为这里许多答案可能忽略了StringBuilder中包含的一个高质量方法:.delete(int start, [int] end)。我知道这是一个晚回复,但这应该被公开(并更详细地解释一下)。

假设您有一个希望在程序中动态修改的StringBuilder表格(我正在处理的一个程序就是这样),例如:

StringBuilder table = new StringBuilder();

如果你正在循环执行该方法并修改内容,使用该内容后,希望将内容丢弃以“清理”StringBuilder以进行下一次迭代,你可以删除其内容,例如:

table.delete(int start, int end). 

如果您想删除的字符的起始和结束索引已知,可以使用这个方法。但是如果您不知道字符的长度并且希望将整个字符都删除怎么办?

table.delete(0, table.length());

现在,重点来了。如前所述,当经常更改时,StringBuilders 会产生很多开销(并可能导致线程安全问题); 因此,如果您的 StringBuilder 用于与用户进行交互,请使用 StringBuffer - 它与 StringBuilder 相同(有一些例外)。

很想知道这篇帖子被踩的原因是什么? - Thomas
1
从JDK 5版本开始,这个类已经被一个等效的类StringBuilder所取代,该类适用于单线程。通常应该优先使用StringBuilder类,因为它支持所有相同的操作,但速度更快,因为它不执行同步操作。换句话说,您对线程是正确的,但对性能是错误的。 - drojf
@drojf 谢谢!很快会更新。 - Thomas

5
如果性能是主要问题,那么讽刺的是,在我看来,Java用于格式化输入缓冲区的构造比分配/重新分配/垃圾回收...可能除了GC(垃圾回收)外,更加耗费CPU时间。这取决于您创建和丢弃的生成器数量。
但是,简单地将复合字符串("Hello World of " + 6E9 + " earthlings.")附加到缓冲区中可能会使整个问题变得微不足道。
而且,如果涉及StringBuilder实例,则内容较为复杂或长度超过简单的String str =“Hi”;(更不用说Java可能在后台使用builder了)。
就我个人而言,我尽量不滥用GC。因此,如果需要在快速连续的场景下频繁使用它 - 比如编写调试输出消息 - 我会在其他地方声明并将其清零以便重复使用。
class MyLogger {
    StringBuilder strBldr = new StringBuilder(256);

    public void logMsg( String stuff, SomeLogWriterClass log ) {

        // zero out strBldr's internal index count, not every
        // index in strBldr's internal buffer
        strBldr.setLength(0);

        // ... append status level
        strBldr.append("Info");

        // ... append ' ' followed by timestamp
        // assuming getTimestamp() returns a String
        strBldr.append(' ').append(getTimestamp());

        // ... append ':' followed by user message
        strBldr.append(':').append(msg);

        log.write(strBldr.toString());
    }
}

只有在您不介意实例大小永远不会缩小的情况下才使用。 - mauhiz
4
你是不是用运算符+来连接字符串只是为了展示一些东西,还是这只是代码异味? - Vlasec
1
@mauhiz strBldr.trimToSize(); 会在设置长度后释放任何未使用的空间。不幸的是,如果该对象经常被使用,那么你只会导致内存波动,因此最好在使用 .setLength(0) 之前使用它,而不是之后。 - Chinoto Vokro

5
StringBuilder s = new StringBuilder();
s.append("a");
s.append("a");
// System.out.print(s); is return "aa"
s.delete(0, s.length());
System.out.print(s.length()); // is return 0

这是简便的方法。

9
你认为这是最好的方式的原因是什么?对我来说,它看起来比setLength(0)变体更丑。 - Vlasec
1
删除调用允许您从StringBuilder对象中删除子字符串;而setLength(0)或setLength(n)仅允许您修改StringBuilder对象的容量。换句话说,两者都适用于完全删除,但delete()具有更多的功能。 - aidanmelen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接