Лучше снова использовать StringBuilder в цикле?

97
задан Raedwald 4 July 2014 в 13:49
поделиться

12 ответов

Увы я обычно нахожусь на C#, я думаю что, очищая StringBuilder веса больше, чем instanciating новый.

0
ответ дан Florian Greinacher 5 November 2019 в 02:35
поделиться

Второй приблизительно на 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
67
ответ дан Epaga 24 November 2019 в 05:28
поделиться

Объявите однажды и присвойтесь каждый раз. Это - более прагматическое и допускающее повторное использование понятие, чем оптимизация.

-2
ответ дан Peter Mortensen 24 November 2019 в 05:28
поделиться

Первое лучше для людей. Если второе немного быстрее на некоторых версиях некоторого JVMs, и что?

, Если производительностью является настолько критический, обходной StringBuilder и запись Ваше собственное. Если Вы - хороший программист и принимаете во внимание, как Ваше приложение использует эту функцию, необходимо быть в состоянии сделать его еще быстрее. Стоящий? Вероятно, нет.

, Почему этот вопрос смотрится как "любимый вопрос"? Поскольку оптимизация производительности является такой забавой, неважно, практично ли это или нет.

0
ответ дан dongilmore 24 November 2019 в 05:28
поделиться

Современная JVM действительно умна о материале как это. Я не был бы второе предположение это, и сделайте что-то hacky, который менее удобен в сопровождении/читаем..., если Вы не делаете надлежащие точки отсчета с производственными данными, которые проверяют нетривиальное повышение производительности (и зарегистрируйте его;)

4
ответ дан Stu Thompson 24 November 2019 в 05:28
поделиться

Так как я не думаю, что на это указали все же из-за оптимизации, встроенной в компилятор Java Sun, который автоматически создает StringBuilders (StringBuffers pre-J2SE 5.0), когда это видит Конкатенации строк, первый пример в вопросе эквивалентен:

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

, Который более читаем, IMO, лучший подход. Ваши попытки оптимизировать могут привести к усилениям в некоторой платформе, но потенциально другим потерь.

, Но если Вы действительно сталкиваетесь с проблемами с производительностью, тогда уверенной, оптимизируют далеко. Я запустил бы с явного определения размера буфера StringBuilder хотя на Jon Skeet.

9
ответ дан Jack Leow 24 November 2019 в 05:28
поделиться

Хорошо, я теперь понимаю то, что продолжается, и это действительно имеет смысл.

у меня создалось впечатление, что toString просто передал лежание в основе char[] в конструктора String, который не сделал , делают копию. Копия была бы тогда сделана на следующей операции "записи" (например, delete). Я полагаю, что это было случай с StringBuffer в некоторой предыдущей версии. (Это не теперь.), Но нет - toString просто передачи массив (и индекс и длина) общественности String конструктор, который делает копию.

Так в "повторном использовании StringBuilder" случай мы действительно создаем одну копию данных на строку, с помощью того же массива символов в буфере все время. Очевидно, создавая новое StringBuilder каждый раз создает новый базовый буфер - и затем что буфер скопирован (несколько бессмысленно, в нашем особом случае, но сделан из соображений безопасности) при создании новой строки.

Все это приводит к второй версии, определенно являющейся более эффективным - но в то же время я все еще сказал бы, что это - более ужасный код.

12
ответ дан Dave Jarvis 24 November 2019 в 05:28
поделиться

В философии записи тела кодируют всегда лучше для помещения StringBuilder в цикле. Таким образом, это не выходит за пределы кода его намеченное для.

, Во-вторых, самый большой improvment в StringBuilder прибывает из предоставления его начальный размер для предотвращения его становящийся больше, в то время как цикл работает

for (loop condition) {
  StringBuilder sb = new StringBuilder(4096);
}
25
ответ дан Jon Skeet 24 November 2019 в 05:28
поделиться

На основе моего опыта с разработкой программного обеспечения в Windows I сказал бы, что убирание StringBuilder во время Вашего цикла имеет лучшую производительность, чем инстанцирование StringBuilder с каждым повторением. Очистка его освобождает ту память, которая будет сразу перезаписана без требуемых дополнительных ассигнований. Я не достаточно знаком со сборщиком "мусора" Java, но я думал бы, что освобождение и никакое перераспределение (если Ваша следующая строка не выращивает StringBuilder) более выгодно, чем инстанцирование.

(Мое мнение противоречит тому, что все остальные предлагают. Хм. Время для сравнительного тестирования его.)

4
ответ дан cfeduke 24 November 2019 в 05:28
поделиться

Я не думаю, что это делает с тех пор, чтобы попытаться оптимизировать производительность как этот. Сегодня (2019) оба statments выполняют приблизительно 11 секунд для 100 000 000 циклов на моем Ноутбуке I5:

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

==> 11 000 мс (объявление в цикле) и 8 236 мс (объявление вне цикла)

, Даже если I'am под управлением программы для адреса dedublication с некоторым миллиардом циклов различие 2 секунд. поскольку 100 миллионов циклов не имеют никакого значения, потому что это программирует, работают в течение многих часов. Также знайте, что вещи отличаются, если Вы только имеете, каждый добавляет оператор:

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

==> 3 416 мс (в цикле), 3 555 мс (вне цикла) первый оператор, который создает StringBuilder в цикле, быстрее в этом случае. И при изменении порядка выполнения, это намного больше быстрее:

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

==> 3 638 мс (вне цикла), 2 908 мс (в цикле)

С уважением, Ulrich

0
ответ дан 24 November 2019 в 05:28
поделиться

Еще быстрее:

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 даст небольшой выигрыш.

23
ответ дан 24 November 2019 в 05:28
поделиться

Причина, по которой выполняется '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% быстрее, чем создание буфера ожидаемого размера.

1
ответ дан 24 November 2019 в 05:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: