鉴于使用Android开发时,重用对象比创建新对象要好,删除StringBuilder的内容并重用它值得吗?

 StringBuilder b = new StringBuilder();
//build up a string
b.delete(0, b.length());
//reuse it
 


与以下内容相比:

 StringBuilder b1 = new StringBuilder();
//build up a string
StringBuilder b2 = new StringBuilder();
//create a new one
 


评论

只要它不是处于非常紧密的循环中,这甚至值得考虑吗?

@Bobby-我需要在一个类中构建十几个不同大小的单独字符串,有些需要循环,有些则不需要。不确定是否值得,所以这就是为什么我问:)

不,我的意思是说这有点像过早的优化。如果您开始担心“需要连接的十几个字符串”而没有任何概要分析,那么您的大脑就会打结。我并不是说这是侮辱,而是作为一种温暖的警告……在那里做。经验法则:如果将字符串串联在一起,请使用StringBuilder。第二个经验法则:如果您担心速度,请先配置。

没有采取任何侮辱并指出要点。说实话,我对优化不是太在意,我是一个没有经验的程序员(或者你能说)。我只是想知道这是否会在某种程度上改善性能。鉴于StringBuilders没有.clear()方法,我一半希望有人说,遍历缓冲区以擦除其内容实际上比垃圾回收更昂贵。永远不要问,永远不知道。

没错我认为两者之间的差异不仅很小,而且几乎不明显。我只是想确保您了解这个问题几乎与现实世界中的优化有关

#1 楼

重用不需要分配内存,因此可以像@Mike Nakis提到的那样更快。另一方面,它导致代码很丑陋:您将相同的变量用于两个(或多个)不同的目的,这可能会使读者感到困惑,使维护工作更加困难。除非您有充分的理由避免这样做。我的意思是,如果分析表明创建新的StringBuilder实例是应用程序中的瓶颈,您就有充分的理由。

如果您真的必须重用StringBuilder,请尝试使用帮助程序以不同的名称访问它们方法:

public static StringBuilder reuseForBetterPerformance(final StringBuilder sb) {
    sb.delete(0, sb.length());
    return sb;
}


客户代码:

final StringBuilder b = new StringBuilder();
//build up a string
final StringBuilder c = reuseForBetterPerformance(b);
//reuse it
c.append(...)


它使代码更具可读性,因为方法名称说明了为什么要重用StringBuilder

评论


\ $ \ begingroup \ $
我不明白决赛的目的
\ $ \ endgroup \ $
– Xavier Combelle
2012年1月8日14:31

\ $ \ begingroup \ $
如果还没有定型,则可以创建一个新的返回它,但是我们想返回相同的对象但将其清空。
\ $ \ endgroup \ $
–luketorjussen
2012年1月8日在16:14

\ $ \ begingroup \ $
final帮助读者,因为他们知道引用始终指向同一实例,并且以后不会更改。虽然是programms.stackexchange.com/questions/115690/…,但是您可以在该主题中找到有关Programmers.SE的其他问题。
\ $ \ endgroup \ $
–palacsint
2012年1月8日17:25

\ $ \ begingroup \ $
辅助功能是一个很好的折衷方案。谢谢你的主意。
\ $ \ endgroup \ $
–克里斯托弗(Christopher)
2012年1月10日上午9:22

#2 楼

要添加一些硬数字,我编写了一个简单的测试应用程序:

public static void main(String[] args) {
    int iterations = 1000000;
    int secondIterations = 25;

    long start = System.currentTimeMillis();
    for (int count = 0; count < iterations; count++) {
        StringBuilder builder = new StringBuilder();
        for (int secondCounter = 0; secondCounter < secondIterations; secondCounter++) {
            builder.append("aaassssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
        }
    }
    System.out.println("Recreation took: " + Long.toString(System.currentTimeMillis() - start) + "ms");

    start = System.currentTimeMillis();
    StringBuilder builder = new StringBuilder();
    for (int count = 0; count < iterations; count++) {
        builder.delete(0, builder.length());
        for (int secondCounter = 0; secondCounter < secondIterations; secondCounter++) {
            builder.append("aaassssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss");
        }
    }
    System.out.println("Reuse took: " + Long.toString(System.currentTimeMillis() - start) + "ms");
}


这给出了以下输出:

Recreation took: 10594ms
Reuse took: 1937ms


所以,是的,因为重用确实确实更快,至少在以较小内存占用的紧密循环中进行测试的情况下。但是请记住,这些数字是使用一百万次迭代生成的。

请牢记两个规则:


首先要考虑可读性和可维护性。
第二...运行探查器。

在真正遇到性能问题之前,我在这里看到的这种差异完全不用担心。

评论


\ $ \ begingroup \ $
哇,快5倍。尽管我愿意打赌,这种差异的大部分是由于您的“娱乐”循环生成2.2 GB的垃圾,所以在“重用”循环产生大量垃圾的同时,请练习垃圾收集器(有损于此)。垃圾少了,几乎没有。
\ $ \ endgroup \ $
–迈克·纳基斯(Mike Nakis)
2015年9月23日在18:23



#3 楼

是的,即使在Android之外的广阔世界中,这当然也是值得的。

根据有关StringBuilder的文档:


每个字符串生成器都有一个容量。只要字符串生成器中包含的字符序列的长度不超过容量,就不必分配新的内部缓冲区。如果内部缓冲区溢出,则会自动变大。


这意味着删除StringBuilder的内容将不会对其char的内部数组进行内存分配操作(它会将其长度设置为零并返回,并保持其最后的容量)。另外,您可以保存StringBuilder对象本身。

评论


\ $ \ begingroup \ $
感谢您的回复对我非常有帮助,因此在这里我有一个问题:1.字符串生成器的默认容量是多少。 2.因此,如果StringBuilder在该时间动态调整其缓冲区大小时会分配新的内存,请确认。
\ $ \ endgroup \ $
–Utsav
15年1月16日在6:45

\ $ \ begingroup \ $
默认的初始容量为16,但是您可以在调用StringBuilder的构造函数时将其设置为所需的任何值。说StringBuilder将动态调整其缓冲区大小就等于说StringBuilder将分配新的内存。
\ $ \ endgroup \ $
–迈克·纳基斯(Mike Nakis)
15年1月17日在8:17