java string和stringbuilder的区别是什么 在循环中重用StringBuilder会更好吗?




string和stringbuilder的区别是什么 (12)

没有明显加快,但是从我的测试来看,使用1.6.0_45 64位平均显示速度提高了几个毫秒:使用StringBuilder.setLength(0)代替StringBuilder.delete():

time = System.currentTimeMillis();
StringBuilder sb2 = new StringBuilder();
for (int i = 0; i < 10000000; i++) {
    sb2.append( "someString" );
    sb2.append( "someString2"+i );
    sb2.append( "someStrin4g"+i );
    sb2.append( "someStr5ing"+i );
    sb2.append( "someSt7ring"+i );
    a = sb2.toString();
    sb2.setLength(0);
}
System.out.println( System.currentTimeMillis()-time );

我有一个关于使用StringBuilder的性能相关问题。 在一个很长的循环中,我操纵一个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());
}

好吧,我现在明白发生了什么事情,它确实有道理。

我的印象是, toString只是将底层char[]传递给一个String构造函数,它没有拷贝。 然后复制下一个“写入”操作(例如delete )。 我相信在之前版本中, StringBuffer 就是这种情况。 (现在不是。)但是, toString只是将数组(以及索引和长度)传递给需要副本的公共String构造函数。

因此,在“重用StringBuilder ”的情况下,我们真正为每个字符串创建一个数据副本,整个过程在缓冲区中使用相同的char数组。 很明显,每次创建一个新的StringBuilder都会创建一个新的底层缓冲区 - 然后在创建新字符串时复制该缓冲区(在我们的特殊情况下有点毫无意义,但出于安全原因)。

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


大声笑,我第一次看到人们通过在StringBuilder中组合字符串来比较性能。 为此,如果你使用“+”,它可能会更快; D。 使用StringBuilder加速检索整个字符串的目的是“locality”的概念。

在经常检索不需要频繁更改的String值的情况下,Stringbuilder允许更高的字符串检索性能。 这就是使用Stringbuilder的目的..请不要MIS - 测试它的核心目的..

有人说,飞机飞得更快。 因此,我用我的自行车进行测试,发现飞机移动速度较慢。 你知道我如何设置实验设置; D


在编写可靠代码的哲学中,最好将您的StringBuilder放入循环中。 这样它就不会超出其预期的代码范围。

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

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

为什么执行'setLength'或'delete'会提高性能的原因主要是代码“学习”缓冲区的大小,而不是内存分配。 通常, 我建议让编译器进行字符串优化 。 但是,如果性能很关键,我经常会预先计算缓冲区的预期大小。 默认的StringBuilder大小是16个字符。 如果你超出了这个范围,那么它必须调整大小。 调整大小就是性能下降的地方。 这是另一个迷你基准,它说明了这一点:

private void clear() throws Exception {
    long time = System.currentTimeMillis();
    int maxLength = 0;
    StringBuilder sb = new StringBuilder();

    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" ).append( i );
        sb.append( "someStrin4g" ).append( i );
        sb.append( "someStr5ing" ).append( i );
        sb.append( "someSt7ring" ).append( i );
        maxLength = Math.max(maxLength, sb.toString().length());
    }

    System.out.println(maxLength);
    System.out.println("Clear buffer: " + (System.currentTimeMillis()-time) );
}

private void preAllocate() throws Exception {
    long time = System.currentTimeMillis();
    int maxLength = 0;

    for( int i = 0; i < 10000000; i++ ) {
        StringBuilder sb = new StringBuilder(82);
        sb.append( "someString" );
        sb.append( "someString2" ).append( i );
        sb.append( "someStrin4g" ).append( i );
        sb.append( "someStr5ing" ).append( i );
        sb.append( "someSt7ring" ).append( i );
        maxLength = Math.max(maxLength, sb.toString().length());
    }

    System.out.println(maxLength);
    System.out.println("Pre allocate: " + (System.currentTimeMillis()-time) );
}

public void testBoth() throws Exception {
    for(int i = 0; i < 5; i++) {
        clear();
        preAllocate();
    }
}

结果显示,重复使用该对象的速度比创建预期大小的缓冲区快大约10%。


唉,我通常在C#上,我认为清理一个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

申报一次,并分配每次。 与优化相比,这是一个更实用,更可重用的概念。


最快的方法是使用“setLength”。 它不会涉及复制操作。 创建一个新的StringBuilder的方法应该完全没有了 。 StringBuilder.delete(int start,int end)的缓慢是因为它将再次为调整大小的部分复制数组。

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

之后,StringBuilder.delete()会将StringBuilder.count更新为新的大小。 虽然StringBuilder.setLength()只是简化了将StringBuilder.count更新为新的大小。


第一个对人类更好。 如果第二个版本在某些版本的JVM上稍微快一点,那又如何?

如果性能非常关键,请绕过StringBuilder并编写自己的。 如果你是一名优秀的程序员,并考虑到你的应用程序如何使用这个功能,你应该能够更快地完成它。 值得吗? 可能不会。

为什么这个问题被视为“最喜欢的问题”? 因为性能优化非常有趣,无论它是否实用。


现代的JVM对于像这样的东西真的很聪明。 我不会再猜测它,做一些不易维护/可读的黑客行为,除非你用生产数据做适当的基准测试,以验证不重要的性能改进(并记录它);


由于我认为它还没有被指出,因为Sun Java编译器内置了优化,当它看到String串联时会自动创建StringBuilders(StringBuffers pre-J2SE 5.0),问题的第一个示例等同于:

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

更可读的IMO是更好的方法。 您尝试优化可能会在某些平台上获得收益,但可能会损失其他平台。

但是,如果你真的遇到了性能问题,那么一定要优化一下。 尽管按照Jon Skeet的说法,我会开始明确指定StringBuilder的缓冲区大小。





stringbuilder