Is it better to reuse a StringBuilder in a loop?

前端 未结 14 2057
名媛妹妹
名媛妹妹 2020-12-04 06:46

I\'ve a performance related question regarding use of StringBuilder. In a very long loop I\'m manipulating a StringBuilder and passing it to another method like

相关标签:
14条回答
  • 2020-12-04 07:23

    The fastest way is to use "setLength". It won't involve the copying operation. The way to create a new StringBuilder should be completely out. The slow for the StringBuilder.delete(int start, int end) is because it will copy the array again for the resizing part.

     System.arraycopy(value, start+len, value, start, count-end);
    

    After that, the StringBuilder.delete() will update the StringBuilder.count to the new size. While the StringBuilder.setLength() just simplify update the StringBuilder.count to the new size.

    0 讨论(0)
  • 2020-12-04 07:24

    The second one is about 25% faster in my mini-benchmark.

    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 );
        }
    }
    

    Results:

    25265
    17969
    

    Note that this is with JRE 1.6.0_07.


    Based on Jon Skeet's ideas in the edit, here's version 2. Same results though.

    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 );
        }
    }
    

    Results:

    5016
    7516
    
    0 讨论(0)
  • 2020-12-04 07:24

    Faster still:

    public class ScratchPad {
    
        private static String a;
    
        public static void main( String[] args ) throws Exception {
            final long time = System.currentTimeMillis();
    
            // Pre-allocate enough space to store all appended strings.
            // StringBuilder, ultimately, uses an array of characters.
            final 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( final String aString ) {
            a = aString;
        }
    }
    

    In the philosophy of writing solid code, the inner workings of the method are hidden from the client objects. Thus it makes no difference from the system's perspective whether you re-declare the StringBuilder within the loop or outside of the loop. Since declaring it outside of the loop is faster, and it does not make the code significantly more complicated, reuse the object.

    Even if it was much more complicated, and you knew for certain that object instantiation was the bottleneck, comment it.

    Three runs with this answer:

    $ java ScratchPad
    1567
    $ java ScratchPad
    1569
    $ java ScratchPad
    1570
    

    Three runs with the other answer:

    $ java ScratchPad2
    1663
    2231
    $ java ScratchPad2
    1656
    2233
    $ java ScratchPad2
    1658
    2242
    

    Although not significant, setting the StringBuilder's initial buffer size, to prevent memory re-allocations, will give a small performance gain.

    0 讨论(0)
  • 2020-12-04 07:25

    I don't think that it make sence to try to optimize performance like that. Today (2019) the both statments are running about 11sec for 100.000.000 loops on my I5 Laptop:

        String a;
        StringBuilder sb = new StringBuilder();
        long time = 0;
    
        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            StringBuilder sb3 = new StringBuilder();
            sb3.append("someString");
            sb3.append("someString2");
            sb3.append("someStrin4g");
            sb3.append("someStr5ing");
            sb3.append("someSt7ring");
            a = sb3.toString();
        }
        System.out.println(System.currentTimeMillis() - time);
    
        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            sb.setLength(0);
            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);
    

    ==> 11000 msec(declaration inside loop) and 8236 msec(declaration outside loop)

    Even if I'am running programms for address dedublication with some billion loops a difference of 2 sec. for 100 million loops does not make any difference because that programs are running for hours. Also be aware that things are different if you only have one append statement:

        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            StringBuilder sb3 = new StringBuilder();
            sb3.append("someString");
                a = sb3.toString();
        }
        System.out.println(System.currentTimeMillis() - time);
    
        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            sb.setLength(0);
            sb.delete(0, sb.length());
            sb.append("someString");
            a = sb.toString();
        }
        System.out.println(System.currentTimeMillis() - time);
    

    ==> 3416 msec(inside loop), 3555 msec(outside loop) The first statement which is creating the StringBuilder within the loop is faster in that case. And, if you change the order of execution it is much more faster:

        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            sb.setLength(0);
            sb.delete(0, sb.length());
            sb.append("someString");
            a = sb.toString();
        }
        System.out.println(System.currentTimeMillis() - time);
    
        System.gc();
        time = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            StringBuilder sb3 = new StringBuilder();
            sb3.append("someString");
                a = sb3.toString();
        }
        System.out.println(System.currentTimeMillis() - time);
    

    ==> 3638 msec(outside loop), 2908 msec(inside loop)

    Regards, Ulrich

    0 讨论(0)
  • Declare once, and assign each time. It is a more pragmatic and reusable concept than an optimization.

    0 讨论(0)
  • 2020-12-04 07:27

    In the philosophy of writing solid code its always better to put your StringBuilder inside your loop. This way it doesnt go outside the code its intended for.

    Secondly the biggest improvment in StringBuilder comes from giving it an initial size to avoid it growing bigger while the loop runs

    for (loop condition) {
      StringBuilder sb = new StringBuilder(4096);
    }
    
    0 讨论(0)
提交回复
热议问题