当前位置:  开发笔记 > 编程语言 > 正文

在循环中重用StringBuilder会更好吗?

如何解决《在循环中重用StringBuilder会更好吗?》经验,为你挑选了5个好方法。

我有关于使用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初始缓冲区大小会产生很小的增益.



1> Epaga..:

第二个在我的迷你基准测试中快了大约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,其并发检查会使速度优势无效.
我在答案中添加了一个编辑来解释为什么会发生这种情况.我会在一段时间内仔细观察(45分钟).请注意,在append调用中进行连接会降低使用StringBuilder的重要性:)
同样有趣的是看看如果你颠倒两个块会发生什么 - 在第一次测试期间JIT仍在"预热"StringBuilder.这可能是无关紧要的,但尝试有趣.

2> Peter..:

在编写可靠代码的哲学中,将StringBuilder放在循环中总是更好.这样它就不会超出其预期的代码范围.

其次,StringBuilder中最大的改进来自给它一个初始大小,以避免它在循环运行时变大

for (loop condition) {
  StringBuilder sb = new StringBuilder(4096);
}



3> Dave Jarvis..:

更快:

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初始缓冲区大小会产生很小的增益.


这是迄今为止最好的答案.StringBuilder由char数组支持,并且是可变的.在调用.toString()时,char数组被复制并用于备份不可变的字符串.此时,只需将插入指针移回零(通过.setLength(0)),就可以重用StringBuilder的可变缓冲区.这些答案表明每个循环分配一个全新的StringBuilder似乎没有意识到.toString创建了另一个副本,因此每个迭代需要两个缓冲区而不是.setLength(0)方法,每个循环只需要一个新的缓冲区.

4> Jon Skeet..:

好的,我现在明白发生了什么,它确实有意义.

我的印象是toString刚刚将底层传递给char[]了一个没有复制的String构造函数.然后将在下一次"写入"操作(例如delete)上进行复制.我相信在以前的版本中就是这种情况StringBuffer.(现在不是.)但是没有 - toString只是将数组(以及索引和长度)传递给String需要复制的公共构造函数.

因此,在"重用StringBuilder"的情况下,我们真正为每个字符串创建一个数据副本,在整个缓冲区中使用相同的char数组.显然StringBuilder每次创建一个新的创建一个新的底层缓冲区 - 然后在创建一个新字符串时复制该缓冲区(在我们的特定情况下,有些无意义,但出于安全原因).

所有这些导致第二个版本肯定更高效 - 但同时我仍然说它是更丑陋的代码.



5> Jack Leow..:

由于我不认为它已被指出,因为Sun Java编译器内置了优化,当它看到String连接时会自动创建StringBuilders(StringBuffers pre-J2SE 5.0),问题中的第一个示例相当于:

for (loop condition) {
  String s = "some string";
  . . .
  s += anotherString;
  . . .
  passToMethod(s);
}

哪个更具可读性,IMO,更好的方法.您尝试优化可能会导致某些平台获益,但可能会损失其他平台.

但如果你真的遇到性能问题,那么肯定,优化.我首先明确指定StringBuilder的缓冲区大小,每个Jon Skeet.

推荐阅读
Gbom2402851125
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有