Clearing a string buffer/builder after loop
在循环之后,如何清除Java中的字符串缓冲区,以便下一次迭代使用一个清晰的字符串缓冲区?
- 另一个问题:为什么只对循环的一次迭代使用sb?还有其他内部迭代吗?如果你只想做一个+b+c+d(Java编译器将内部使用一个SB),SB就不值得。如果您想有条件地添加字符串的一部分,这会有所帮助,但另外…简单地使用"+"。
一种选择是使用如下的删除方法:
1 2 3 4 5 6 7
| StringBuffer sb = new StringBuffer();
for (int n = 0; n < 10; n ++) {
sb. append("a");
// This will clear the buffer
sb. delete(0, sb. length());
} |
另一个选项(位清理器)使用setlength(int len):
有关更多信息,请参阅javadoc:
- 稍微少一点的垃圾就是在循环中声明StringBuffer。
- 啊,我认为sb.setlength(0);比在循环中声明它更干净、更有效。您的解决方案违背了使用StringBuffer的性能优势…
- 我认为性能优势来自于字符串的可变性,而不是来自于保存实例化。下面是对1E8迭代的快速测试:内部循环(2.97s):ideone.com/uytl14w,外部循环(2.87s):ideone.com/f9lgsixh
- 我认为这不是一个非常精确的微基准。
- 说到性能:除非您的代码是在多线程场景中访问的,否则您应该使用StringBuilder而不是StringBuffer——请参阅javadoc:"如果可能,建议使用这个类而不是StringBuffer,因为在大多数实现中它会更快。"
- 我同意Mark的观点,最好是在循环中创建对象,并将引用保留在外部……
- 在外部创建sb的唯一好处是不会丢失它的内部(可能很长)字符[]。如果在第一个迭代器中增长足够大,则第二个循环将不需要调整任何char[]的大小。但是为了获得优势,"clear方法"必须保留内部数组的大小。setlength可以做到这一点,但它也会将sb中所有未使用的字符设置为u0000,因此仅仅创建一个具有良好初始容量的新sb的性能就比较差。在循环中声明更好。
- 如果您所做的是在循环的迭代中累积一个值,那么在循环内部声明就不起作用。例如:Map map; StringBuffer buffer = new StringBuffer(); for (String arg : args) { if (arg ...) { buffer.append(arg); } else if (arg ...) { map.put(arg, buffer.toString()); // Clear the buffer here. }您可以在需要清除的地方重新创建缓冲区,但是缓冲区不能完全包含在循环中,或者在点之外。
- @Markelliot不会增加内存使用量吗?
- 是的,我的评论很愚蠢,不知道我在想什么=/
- 查看StringBuilder的代码,setLength清除缓冲区(但它使用的是array.fill,速度非常快),而delete没有清除缓冲区,但它确实尝试执行零字节的数组复制。
重用StringBuffer的最简单方法是使用setLength()方法。
public void setLength(int newLength)
你可能有这样的情况
1 2 3 4
| StringBuffer sb = new StringBuffer("HelloWorld");
// after many iterations and manipulations
sb. setLength(0);
// reuse sb |
- @mmahmoud,在代码示例中,它应该读取setLength,而不是setLength。
- 当我看到setlength代码源时,它是如何工作的(gist.github.com/ebuildy/e91ac6af2f8a6b1821d18abf2f8c9e1),这里的计数总是大于newlength(0)?
您有两种选择:
要么使用:
1
| sb.setLength(0); // It will just discard the previous data, which will be garbage collected later. |
或使用:
1
| sb.delete(0, sb.length()); // A bit slower as it is used to delete sub sequence. |
注释
避免在循环中声明StringBuffer或StringBuilder对象,否则它将在每次迭代中创建新对象。创建对象需要系统资源、空间和时间。因此,从长远来看,如果可能,避免在循环中声明它们。
我建议为每个迭代创建一个新的StringBuffer(甚至更好,StringBuilder)。性能差异确实可以忽略不计,但是您的代码将变得更短和简单。
- 由于Java现在主要在Android上使用,所以我不确定在一个循环中分配一个新的对象是一个很好的性能和电池寿命的实践。清除StringBuilder所增加的代码复杂性相对于潜在的性能增益是最小的。
- 如果您有任何证据表明您的表现存在差异,请与我们分享。(我也很高兴看到Java主要使用哪里的统计数据,以及在哪种定义下"大部分"。
- 众所周知,分配(Java中的新关键字)比只更改同一对象的某些字段更昂贵。对于"大多数",你可以高度的复制,有超过15亿的Android设备,全部使用Java。
- "潜在业绩增长"的主张最好以具体的证据为依据,例如这一具体案例的基准。然后,根据需求和应用程序的不同,每个开发人员可以在性能和代码可读性之间做出权衡决定。
- 我同意在某些情况下,代码可读性比性能更重要,但在这种情况下,它只是一行代码!您可以运行一个基准测试,它将证明分配和对象创建,再加上垃圾收集比根本不进行更昂贵。因为我们是在一个循环中,所以它更相关。
- 没有单一的"本例";数据库约束的服务器端代码"本例"的约束、要求和考虑与实时系统的"本例"不同,这与Android应用程序的"本例"也不同。我从来没有说过没有性能差异,我说过它通常可以忽略不计(也就是说,你的性能被其他因素所支配)。根据我的个人经验,可读性通常比性能更重要(在理智的程度上),但底线是每个开发人员都将自己决定权衡。
- 小开销,如不必要的分配、垃圾收集(运行时可能会冻结应用程序、丢弃帧)等,是小性能问题,与其他应用程序和其他地方一起,会导致电池寿命差、性能差、用户体验差,并与其他应用程序一起累积,这是一个很大的开销。精液
- 给每个人。我大多赞同Knuth的观点,即过早优化是万恶之源。我想我真的很喜欢使用一个Android应用程序,它的整体性能非常好,以至于在一个循环中初始化StringBuffers会严重影响它。
- 我也赞同克努斯的观点,但他的观点并不总是正确的。只添加一行就可能获得CPU周期和内存,这绝对不是坏事。你的想法太直截了当了。请注意,循环通常重复多次。一个循环可以重复数千次,如果不仔细优化,它可以在移动设备(以及服务器或计算机)上消耗宝贵的兆字节。
- 我认为我们都清楚地阐述了自己的观点,我认为进一步的讨论对这个评论部分没有好处。如果你觉得我的回答不正确、误导或不完整,那么你——或者任何人——无论如何都欢迎编辑和/或否决它。
1
| buf.delete(0, buf.length()); |
1 2 3
| public void clear(StringBuilder s) {
s.setLength(0);
} |
用途:
1 2
| StringBuilder v = new StringBuilder();
clear(v); |
对于可读性,我认为这是最好的解决方案。
答案已经很好了。只需为StringBuffer和StringBuild性能差异添加一个基准结果,即在循环中使用新实例或在循环中使用setLength(0)。
总结是:在一个大循环中
- StringBuilder比StringBuffer快得多
- 在循环中新建StringBuilder实例没有与设定长度(0)的差异。(setlength(0)比创建新实例有非常小的优势。)
- 通过在循环中创建新实例,StringBuffer比StringBuilder慢
- StringBuffer的setLength(0)非常慢于在循环中创建新实例。
非常简单的基准测试(我只是手动更改代码并执行不同的测试):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class StringBuilderSpeed {
public static final char ch [] = new char[]{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i'};
public static void main (String a []){
int loopTime = 99999999;
long startTime = System. currentTimeMillis();
StringBuilder sb = new StringBuilder ();
for(int i = 0 ; i < loopTime ; i ++){
for(char c : ch ){
sb. append(c );
}
sb. setLength(0);
}
long endTime = System. currentTimeMillis();
System. out. println("Time cost:" + (endTime - startTime ));
} |
}
循环中的新StringBuilder实例:时间成本:3693386236243742
StringBuilder设置长度:时间成本:3465、3421、3557、3408
循环中的新StringBuffer实例:时间成本:8327、8324、8284
字符串缓冲区设置长度时间成本:22878、23017、22894
同样,为了确保不是我的Labtop,StringBuilder的setLength对StringBuffer的setLength使用了这么长的长度有一些问题:-)时间成本:3448
我认为这个代码更快。
- 这将导致性能问题。它在每次迭代后创建一个新对象
- @尽管这是一个非常合理的方法。没有证据就不要假设性能问题。
- 假设事物基于对所发生的事情的理解,这是智力的基础。