我有关于使用StringBuilder的性能相关问题.在一个很长的循环中,我正在操纵a StringBuilder
并将其传递给另一个方法,如下所示:
for (loop condition) { StringBuilder sb = new StringBuilder(); sb.append("some string"); . . . sb.append(anotherString); . . . passToMethod(sb.toString()); }
StringBuilder
在每个循环周期实例化是一个很好的解决方案吗?并且更好地调用删除,如下所示?
StringBuilder sb = new StringBuilder(); for (loop condition) { sb.delete(0, sb.length); sb.append("some string"); . . . sb.append(anotherString); . . . passToMethod(sb.toString()); }
Epaga.. 67
第二个在我的迷你基准测试中快了大约25%.
public class ScratchPad { static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); for( int i = 0; i < 10000000; i++ ) { StringBuilder sb = new StringBuilder(); sb.append( "someString" ); sb.append( "someString2"+i ); sb.append( "someStrin4g"+i ); sb.append( "someStr5ing"+i ); sb.append( "someSt7ring"+i ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for( int i = 0; i < 10000000; i++ ) { sb.delete( 0, sb.length() ); sb.append( "someString" ); sb.append( "someString2"+i ); sb.append( "someStrin4g"+i ); sb.append( "someStr5ing"+i ); sb.append( "someSt7ring"+i ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); } }
结果:
25265 17969
请注意,这是使用JRE 1.6.0_07.
基于Jon Skeet在编辑中的想法,这里是版本2.但是结果相同.
public class ScratchPad { static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for( int i = 0; i < 10000000; i++ ) { sb.delete( 0, sb.length() ); sb.append( "someString" ); sb.append( "someString2" ); sb.append( "someStrin4g" ); sb.append( "someStr5ing" ); sb.append( "someSt7ring" ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); time = System.currentTimeMillis(); for( int i = 0; i < 10000000; i++ ) { StringBuilder sb2 = new StringBuilder(); sb2.append( "someString" ); sb2.append( "someString2" ); sb2.append( "someStrin4g" ); sb2.append( "someStr5ing" ); sb2.append( "someSt7ring" ); a = sb2.toString(); } System.out.println( System.currentTimeMillis()-time ); } }
结果:
5016 7516
使用sb.setLength(0); 相反,它是清空StringBuilder内容以重新创建对象或使用.delete()的最快方法.请注意,这不适用于StringBuffer,其并发检查会使速度优势无效. (24认同)
我在答案中添加了一个编辑来解释为什么会发生这种情况.我会在一段时间内仔细观察(45分钟).请注意,在append调用中进行连接会降低使用StringBuilder的重要性:) (4认同)
同样有趣的是看看如果你颠倒两个块会发生什么 - 在第一次测试期间JIT仍在"预热"StringBuilder.这可能是无关紧要的,但尝试有趣. (3认同)
Peter.. 25
在编写可靠代码的哲学中,将StringBuilder放在循环中总是更好.这样它就不会超出其预期的代码范围.
其次,StringBuilder中最大的改进来自给它一个初始大小,以避免它在循环运行时变大
for (loop condition) { StringBuilder sb = new StringBuilder(4096); }
Dave Jarvis.. 23
更快:
public class ScratchPad { private static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder( 128 ); for( int i = 0; i < 10000000; i++ ) { // Resetting the string is faster than creating a new object. // Since this is a critical loop, every instruction counts. // sb.setLength( 0 ); sb.append( "someString" ); sb.append( "someString2" ); sb.append( "someStrin4g" ); sb.append( "someStr5ing" ); sb.append( "someSt7ring" ); setA( sb.toString() ); } System.out.println( System.currentTimeMillis()-time ); } private static void setA( String aString ) { a = aString; } }
在编写固体代码的哲学中,应该从使用该方法的对象中隐藏该方法的内部工作方式.因此,无论您是在循环内还是循环外重新声明StringBuilder,系统的视角都没有区别.由于在循环之外声明它更快,并且它不会使代码更复杂,因此重用该对象而不是重新实例化它.
即使代码更复杂,并且您确定对象实例化是瓶颈,请对其进行评论.
这个答案有三次运行:
$ java ScratchPad 1567 $ java ScratchPad 1569 $ java ScratchPad 1570
另外三个回答:
$ java ScratchPad2 1663 2231 $ java ScratchPad2 1656 2233 $ java ScratchPad2 1658 2242
虽然不重要,但设置StringBuilder
初始缓冲区大小会产生很小的增益.
第二个在我的迷你基准测试中快了大约25%.
public class ScratchPad { static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); for( int i = 0; i < 10000000; i++ ) { StringBuilder sb = new StringBuilder(); sb.append( "someString" ); sb.append( "someString2"+i ); sb.append( "someStrin4g"+i ); sb.append( "someStr5ing"+i ); sb.append( "someSt7ring"+i ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for( int i = 0; i < 10000000; i++ ) { sb.delete( 0, sb.length() ); sb.append( "someString" ); sb.append( "someString2"+i ); sb.append( "someStrin4g"+i ); sb.append( "someStr5ing"+i ); sb.append( "someSt7ring"+i ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); } }
结果:
25265 17969
请注意,这是使用JRE 1.6.0_07.
基于Jon Skeet在编辑中的想法,这里是版本2.但是结果相同.
public class ScratchPad { static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder(); for( int i = 0; i < 10000000; i++ ) { sb.delete( 0, sb.length() ); sb.append( "someString" ); sb.append( "someString2" ); sb.append( "someStrin4g" ); sb.append( "someStr5ing" ); sb.append( "someSt7ring" ); a = sb.toString(); } System.out.println( System.currentTimeMillis()-time ); time = System.currentTimeMillis(); for( int i = 0; i < 10000000; i++ ) { StringBuilder sb2 = new StringBuilder(); sb2.append( "someString" ); sb2.append( "someString2" ); sb2.append( "someStrin4g" ); sb2.append( "someStr5ing" ); sb2.append( "someSt7ring" ); a = sb2.toString(); } System.out.println( System.currentTimeMillis()-time ); } }
结果:
5016 7516
在编写可靠代码的哲学中,将StringBuilder放在循环中总是更好.这样它就不会超出其预期的代码范围.
其次,StringBuilder中最大的改进来自给它一个初始大小,以避免它在循环运行时变大
for (loop condition) { StringBuilder sb = new StringBuilder(4096); }
更快:
public class ScratchPad { private static String a; public static void main( String[] args ) throws Exception { long time = System.currentTimeMillis(); StringBuilder sb = new StringBuilder( 128 ); for( int i = 0; i < 10000000; i++ ) { // Resetting the string is faster than creating a new object. // Since this is a critical loop, every instruction counts. // sb.setLength( 0 ); sb.append( "someString" ); sb.append( "someString2" ); sb.append( "someStrin4g" ); sb.append( "someStr5ing" ); sb.append( "someSt7ring" ); setA( sb.toString() ); } System.out.println( System.currentTimeMillis()-time ); } private static void setA( String aString ) { a = aString; } }
在编写固体代码的哲学中,应该从使用该方法的对象中隐藏该方法的内部工作方式.因此,无论您是在循环内还是循环外重新声明StringBuilder,系统的视角都没有区别.由于在循环之外声明它更快,并且它不会使代码更复杂,因此重用该对象而不是重新实例化它.
即使代码更复杂,并且您确定对象实例化是瓶颈,请对其进行评论.
这个答案有三次运行:
$ java ScratchPad 1567 $ java ScratchPad 1569 $ java ScratchPad 1570
另外三个回答:
$ java ScratchPad2 1663 2231 $ java ScratchPad2 1656 2233 $ java ScratchPad2 1658 2242
虽然不重要,但设置StringBuilder
初始缓冲区大小会产生很小的增益.
好的,我现在明白发生了什么,它确实有意义.
我的印象是toString
刚刚将底层传递给char[]
了一个没有复制的String构造函数.然后将在下一次"写入"操作(例如delete
)上进行复制.我相信在以前的版本中就是这种情况StringBuffer
.(现在不是.)但是没有 - toString
只是将数组(以及索引和长度)传递给String
需要复制的公共构造函数.
因此,在"重用StringBuilder
"的情况下,我们真正为每个字符串创建一个数据副本,在整个缓冲区中使用相同的char数组.显然StringBuilder
每次创建一个新的创建一个新的底层缓冲区 - 然后在创建一个新字符串时复制该缓冲区(在我们的特定情况下,有些无意义,但出于安全原因).
所有这些导致第二个版本肯定更高效 - 但同时我仍然说它是更丑陋的代码.
由于我不认为它已被指出,因为Sun Java编译器内置了优化,当它看到String连接时会自动创建StringBuilders(StringBuffers pre-J2SE 5.0),问题中的第一个示例相当于:
for (loop condition) { String s = "some string"; . . . s += anotherString; . . . passToMethod(s); }
哪个更具可读性,IMO,更好的方法.您尝试优化可能会导致某些平台获益,但可能会损失其他平台.
但如果你真的遇到性能问题,那么肯定,优化.我首先明确指定StringBuilder的缓冲区大小,每个Jon Skeet.