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