StringBuilder/StringBuffer против оператора "+"

Я читаю "Лучше, быстрее, легче Java" (Брюс Тейт и Джастин Гетланд), и я знаком с требованиями к удобочитаемости в гибких командах типа, например, что Роберт Мартин обсуждает в своих чистых книгах по кодированию. В команде, в которой я сейчас участвую, мне было сказано явно не использовать оператор +, поскольку он создает дополнительные (и ненужные) строковые объекты во время выполнения.

Но это статья, Написанная в '04, говорит о том, как распределение объектов составляет около 10 машинных команд. (по существу свободный)

В нем также говорится о том, как GC также помогает снизить затраты в этой среде.

Каковы фактические компромиссы между использованием +, StringBuilder или StringBuffer? (В моем случае это StringBuffer, только если мы ограничены Java 1.4.2.)

StringBuffer для меня приводит к уродливому, менее читаемому коду, как показывает несколько примеров в книге Тейта. И StringBuffer синхронизируется по потоку, который, как представляется, имеет свои собственные затраты, которые перевешивают "опасность" при использовании оператора +.

Мысль/мнение?

Ответ 1

Использование String concatenation преобразуется в операции StringBuilder компилятором.

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

Оригинальный класс:

public void method1() {
    System.out.println("The answer is: " + 42);
}

public void method2(int value) {
    System.out.println("The answer is: " + value);
}

public void method3(int value) {
    String a = "The answer is: " + value;
    System.out.println(a + " what is the question ?");
}

Декомпилированный класс:

public void method1()
{
    System.out.println("The answer is: 42");
}

public void method2(int value)
{
    System.out.println((new StringBuilder("The answer is: ")).append(value).toString());
}

public void method3(int value)
{
    String a = (new StringBuilder("The answer is: ")).append(value).toString();
    System.out.println((new StringBuilder(String.valueOf(a))).append(" what is the question ?").toString());
}
  • В method1 компилятор выполнил операцию во время компиляции.
  • В method2 конкатенация String эквивалентна использованию вручную StringBuilder.
  • В method3 конкатенация String определенно плоха, поскольку компилятор создает второй StringBuilder вместо повторного использования предыдущего.

Итак, мое простое правило заключается в том, что конкатенации хороши, если вам не нужно снова конкатенировать результат: например, в цикле или когда вам нужно сохранить промежуточный результат.

Ответ 2

Ваша команда должна узнать о причинах избежания повторной конкатенации строк.

Конечно, времена, когда имеет смысл использовать StringBuffer - в частности, когда вы создаете строку в цикле, особенно если вы не уверены, что в цикле будет несколько итераций. Обратите внимание, что это не просто вопрос создания новых объектов - это вопрос копирования всех текстовых данных, которые вы уже добавили. Также имейте в виду, что выделение объектов является "практически бесплатным", если вы не рассматриваете сборку мусора. Да, если в текущем поколении достаточно места, это в основном вопрос увеличения указателя... но:

  • В какой-то момент память должна быть очищена. Это не бесплатно.
  • Вы сокращаете время до следующего GC. GC не является бесплатным.
  • Если ваш объект живет в следующем поколении, может потребоваться больше времени для очистки - опять же, не бесплатно.

Все эти вещи достаточно дешевы, поскольку "обычно" не стоит сгибать дизайн от элегантности, чтобы избежать создания объектов... но вы не должны считать их свободными.

С другой стороны, нет смысла использовать StringBuffer в тех случаях, когда вам не понадобятся промежуточные строки. Например:

String x = a + b + c + d;

не менее эффективен, чем:

StringBuffer buffer = new StringBuffer();
buffer.append(a);
buffer.append(b);
buffer.append(c);
buffer.append(d);
String x = buffer.toString();

Ответ 3

Для небольших конкатенаций вы можете просто использовать String и + для удобства чтения. Производительность не пострадает. Но если вы делаете много операций конкатенации, отправляйте StringBuffer.

Ответ 4

Рассмотрим следующий тест производительности: мы строим динамическую строку внутри цикла итерации 100000:

public void constructDynamicString()
    {
        long startTime = System.currentTimeMillis();
        String s = "test";
        //StringBuffer s = new StringBuffer("test");
        //StringBuilder s = new StringBuilder("test");
        for(int i=0; i<100000; i++)
        {
            s += "concat";
            //s.append("concat");
        }

        long endTime = System.currentTimeMillis();

        System.out.println("Concat time ====== " + (endTime - startTime));
    }

Мы выполняем вышеуказанный тест 3 раза, каждый подход за раз, и получаем следующий результат:

With String, it tooks: 39068 ms
With StringBuffer, it tooks: 7ms
With StringBuilder, it tooks: 4ms

Из приведенных выше результатов мы заметили, что использование String объекта для создания динамической строки символов медленнее, чем использование StringBuffer и StringBuilder к факту непреложности.

Кроме того, StringBuilder быстрее, чем StringBuffer, поскольку он не заботится о синхронизации (хотя он выглядит немного быстрее, скорость будет относительно отличаться, если мы увеличим количество итераций).

Теперь, чтобы решить, какой класс использовать, учитывайте следующие факторы:

  • Если вы создаете статическую строку символов, которая не должна быть изменена во всем потоке программы, используйте объект String.
  • Если вы создаете динамическую строку символов, которая должна быть разделена между несколькими потоками, рассмотрите возможность использования StringBuffer.
  • Если оба фактора не существуют (это очень распространенный случай), используйте StringBuilder.

Источник: StringBuilder VS StringBuffer