ChatGPT解决这个技术问题 Extra ChatGPT

如何清除或清空 StringBuilder? [复制]

这个问题在这里已经有了答案:Clearing a string buffer/builder after loop (10 answers) Closed 6 years ago。

我在循环中使用 StringBuilder,每 x 次迭代我想清空它并以空 StringBuilder 开始,但我在文档中看不到任何类似于 .NET StringBuilder.Clear 的方法,只是delete 方法似乎过于复杂。

那么在 Java 中清除 StringBuilder 的最佳方法是什么?


P
Per Lundberg

两种工作方式:

使用 stringBuilderObj.setLength(0)。使用 new StringBuilder() 分配一个新的,而不是清除缓冲区。请注意,对于性能关键的代码路径,这种方法可能比基于 setLength 的方法慢得多(因为需要分配具有新缓冲区的新对象,旧对象才有资格进行 GC 等)。


不,它没有那么便宜!你怎么能这么说?假设您有一个容量为 1000 个字符的缓冲区。然后你处理它(为 GC 工作)并创建一个新的(为分配器工作)。将文本长度设置为零(实际上对 CPU 没有任何作用)并重用相同的缓冲区要快得多。
@Sulthan:哦,这个答案迟到了:我在考虑 StringBuffer.delete(idx, len)。另一方面,执行 setLength 要求它迭代整个缓冲区并将每个字符设为空(例如 kickjava.com/src/java/lang/AbstractStringBuilder.java.htm)。根据缓冲区的大小,这也可能很昂贵。另一方面,除非它是超级性能的代码,否则请选择对您来说最清晰的代码,不要花时间进行微优化。
@Marcus,在您作为示例提供的链接中, setLength(0) 不会像您说的那样进行迭代,只有当新长度大于 used-char 计数时才会这样做(长度为 0 时不会发生) .就性能而言,似乎 setLength(0) 是最好的,而且清空缓冲区似乎也是一个非常明确的含义。
@Marcus您应该更新您的答案。
@cafebabe1991 仔细阅读源代码:if (count < newLength),但如果 newLength 为 0,则永远不会发生这种情况。
J
Jörn Horstmann

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

如果您事先知道 StringBuilder 的预期容量,那么每次创建一个新的应该和设置一个新的长度一样快。它也将有助于垃圾收集器,因为每个 StringBuilder 的寿命都相对较短,并且 gc 已为此进行了优化。

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


谢谢,我忘记了带有容量参数的构造函数。
如果您使用 setLength(0),这是否意味着它将内部缓冲区保持在当前长度?我担心我不想 new 一个新的 StringBuffer,因为我希望 有时 我会有相当长的字符串,因此我从一个相当大的缓冲区大小(4k 或 32k)开始.因此,听起来 setLength(0) 可能会更快。但是 - 如果 StringBuffer 分配的空间永远不会缩小,我可能会耗尽内存(这是在 Android 下内存会变得紧张的情况)。
@Michael:是的,内部缓冲区保持在当前长度。您可以在 android.googlesource.com/platform/libcore/+/master/luni/src/… 找到 android 的实际实现。完成附加字符后,您可以使用 trimToSize 方法释放不需要的空间。
您写道:“根据使用情况,两者都有优点和缺点。”你能给我举个例子,什么时候在每次迭代中创建新的 StringBuilder 更好?
@icza 一个例子是如果你想并行处理。
k
krtek

delete 并不过分复杂:

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

你也可以这样做:

myStringBuilder.setLength(0);

复杂可能是错误的词,我的意思是它看起来不那么整洁。
但与执行新分配相比,overly efficient 不是。
这就是为什么我添加了 setLength(0) 版本,它应该更快。但可能新的分配会更快。
setLength 替代方案很有趣,谢谢。
考虑将 stringbuilder 对象作为输出参数传递给函数,那么新分配不是一个选项。
J
Javamann

如果您查看 StringBuilder 或 StringBuffer 的源代码, setLength() 调用只会重置字符数组的索引值。恕我直言,使用 setLength 方法总是比新分配快。他们应该将方法命名为“clear”或“reset”,这样会更清晰。


@FrankHarper:仅当您扩展字符串时。如果你缩小它,Javamann 是正确的。
@FrankHarper 你错了。当 newLength 为零时,源不执行任何操作。
setLength 也会导致内存泄漏,但你会发现为时已晚。所以人们有时会给出非常愚蠢的答案。 setLength 除了将长度设置为零之外什么都不做。剩余的分配仍然存在。这个答案源于数组的javascript length = 0,它执行一个神奇的操作来标记数组可重用,但即使在那里我也不确定,也不相信它。底层数组永远不会被垃圾收集。
P
Peter Mortensen

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

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


不要挑词。只需阅读答案即可了解我的观点。
setLength 也会导致内存泄漏,但你会发现为时已晚。 setLength 除了将长度设置为零之外什么都不做。剩余的分配仍然存在。
@momomo 好处是您可以在不创建新数组的情况下重用它,从而使您免于不必要的 GC 踢,并且当您使用 StringBuilder 完成后,无论如何它都会被垃圾收集。
P
Peter Mortensen

您应该使用 sb.delete(0, sb.length())sb.setLength(0) 而不是创建新的 StringBuilder()。

有关性能,请参阅此相关帖子:Is it better to reuse a StringBuilder in a loop?


T
Thomas

我认为这里的许多答案可能缺少StringBuilder中包含的质量方法:.delete(int start, [int] end)。我知道这是一个迟到的答复;但是,这应该被告知(并更彻底地解释)。

假设您有一个 StringBuilder 表 - 您希望在整个程序中动态修改它(我现在正在做的一个),例如

StringBuilder table = new StringBuilder();

如果您正在循环通过该方法并更改内容,使用该内容,然后希望丢弃该内容以“清理”StringBuilder 以进行下一次迭代,您可以删除它的内容,例如

table.delete(int start, int end). 

start 和 end 是您要删除的字符的索引。不知道字符的长度并想删除整个内容?

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

现在,对于踢球者。 StringBuilders,如前所述,频繁更改时会占用大量开销(并且可能导致线程方面的安全问题);因此,使用 StringBuffer - 与 StringBuilder 相同(有一些例外) - 如果您的 StringBuilder 用于与用户交互的目的。


想知道这个帖子的反对票是什么?
来自 docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html “从 JDK 5 开始,这个类已经补充了一个为单线程使用而设计的等效类,StringBuilder。通常应该优先使用 StringBuilder 类而不是这个类,因为它支持所有相同的操作,但它更快,因为它不执行同步。” - 换句话说,你对线程是正确的,但对性能是错误的。
@drojf 谢谢!会更新的,很快。
Z
Ziem
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

是简单的方法。


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

如果性能是主要关注点,那么在我看来,具有讽刺意味的是,用于格式化进入缓冲区的文本的 Java 构造在 CPU 上将比分配/重新分配/垃圾收集更耗时……嗯,可能不是 GC(垃圾收集),具体取决于您创建和丢弃的构建器数量。

但是简单地将复合字符串 ("Hello World of " + 6E9 + " earthlings.") 附加到缓冲区可能会使整个问题变得无关紧要。

而且,实际上,如果涉及 StringBuilder 的实例,那么内容会比简单的 String str = "Hi"; 复杂和/或更长(不管 Java 可能在后台使用构建器)。

就个人而言,我尽量不滥用 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());
    }
}

仅当您不介意实例大小永远不会缩小时才使用。
您是通过 operator+ 连接字符串以向我们展示一些东西还是只是代码气味?
@mauhiz strBldr.trimToSize(); 将在设置长度后释放任何未使用的空间。不幸的是,如果经常使用该对象,您只会导致内存流失,因此如果您确实使用它,最好在 .setLength(0) 之前使用它,而不是之后使用它。