У меня есть вопрос, связанный с производительностью, относительно использования StringBuilder. В очень длинном цикле я манипулирую a 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());
}
java
performance
string
stringbuilder
Пьер Луиджи
источник
источник
В философии написания надежного кода всегда лучше помещать StringBuilder в свой цикл. Таким образом, он не выходит за рамки кода, для которого он предназначен.
Во-вторых, самое большое улучшение в StringBuilder заключается в том, что ему был задан начальный размер, чтобы он не увеличивался во время выполнения цикла.
источник
Еще быстрее:
Согласно философии написания твердого кода, внутренняя работа метода должна быть скрыта от объектов, которые используют метод. Таким образом, с точки зрения системы не имеет значения, повторно объявляете ли вы StringBuilder внутри цикла или вне цикла. Поскольку объявление его вне цикла происходит быстрее и не усложняет чтение кода, используйте объект повторно, а не создайте его заново.
Даже если код был более сложным, и вы точно знали, что создание экземпляра объекта является узким местом, прокомментируйте его.
Три прогона с этим ответом:
Три пробега с другим ответом:
Хотя это несущественно, установка
StringBuilder
начального размера буфера даст небольшой выигрыш.источник
Хорошо, теперь я понимаю, что происходит, и это имеет смысл.
У меня создалось впечатление, что я
toString
просто передал базовое значениеchar[]
в конструктор String, который не взял копию. Затем копия будет сделана при следующей операции «записи» (напримерdelete
). Я считаю, что это было такStringBuffer
в какой-то предыдущей версии. (Это не сейчас.) Но нет -toString
просто передает массив (а также индекс и длину) общедоступномуString
конструктору, который принимает копию.Таким образом, в случае «повторного использования
StringBuilder
» мы действительно создаем одну копию данных для каждой строки, все время используя один и тот же массив символов в буфере. Очевидно, что создание новогоStringBuilder
каждый раз создает новый базовый буфер - а затем этот буфер копируется (несколько бессмысленно, в нашем конкретном случае, но делается по соображениям безопасности) при создании новой строки.Все это приводит к тому, что вторая версия определенно более эффективна - но в то же время я бы сказал, что это более уродливый код.
источник
Поскольку я не думаю, что на это еще указывалось, из-за оптимизации, встроенной в компилятор Sun Java, который автоматически создает StringBuilders (StringBuffers pre-J2SE 5.0), когда видит конкатенации строк, первый пример в вопросе эквивалентен:
Что более читабельно, ИМО, тем лучше. Ваши попытки оптимизации могут привести к выигрышу для одной платформы, но потенциально к потерям для других.
Но если вы действительно сталкиваетесь с проблемами с производительностью, тогда, конечно, оптимизируйте. Я бы начал с явного указания размера буфера StringBuilder, согласно Джону Скиту.
источник
Современная JVM действительно умна в подобных вещах. Я бы не стал сомневаться в этом и сделать что-то хакерское, менее удобное в обслуживании / читаемом ... если только вы не выполните надлежащие тесты производительности с производственными данными, которые подтверждают нетривиальное улучшение производительности (и документируют это;)
источник
Основываясь на моем опыте разработки программного обеспечения в Windows, я бы сказал, что очистка StringBuilder во время цикла дает лучшую производительность, чем создание экземпляра StringBuilder на каждой итерации. Его очистка освобождает эту память для немедленной перезаписи без дополнительного выделения. Я недостаточно знаком с сборщиком мусора Java, но я думаю, что освобождение и отсутствие перераспределения (если ваша следующая строка не увеличивает StringBuilder) более выгодно, чем создание экземпляра.
(Мое мнение противоречит тому, что предлагают все остальные. Хм. Пора проверить это.)
источник
Причина, по которой выполнение setLength или delete улучшает производительность, в основном заключается в том, что код «изучает» правильный размер буфера, а не в распределении памяти. Как правило, я рекомендую позволить компилятору выполнять оптимизацию строк . Однако, если производительность критична, я часто заранее рассчитываю ожидаемый размер буфера. Размер StringBuilder по умолчанию составляет 16 символов. Если вы вырастете за пределы этого, его размер придется изменить. Изменение размера - вот где теряется производительность. Вот еще один мини-тест, который это иллюстрирует:
Результаты показывают, что повторное использование объекта примерно на 10% быстрее, чем создание буфера ожидаемого размера.
источник
LOL, я впервые увидел, как люди сравнивают производительность, комбинируя строку в StringBuilder. Для этой цели, если вы используете "+", это может быть еще быстрее; D. Цель использования StringBuilder для ускорения извлечения всей строки как понятия «локальность».
В сценарии, когда вы часто извлекаете значение String, которое не требует частого изменения, Stringbuilder обеспечивает более высокую производительность извлечения строки. И это цель использования StringBuilder .. пожалуйста, не тестируйте MIS-Test его основную цель ..
Некоторые говорили: «Самолет летит быстрее». Поэтому я протестировал это на своем байке и обнаружил, что самолет движется медленнее. Вы знаете, как я устанавливаю настройки эксперимента? D
источник
Не значительно быстрее, но из моих тестов показывает, что в среднем на пару миллисек быстрее при использовании 1.6.0_45 64 бит: используйте StringBuilder.setLength (0) вместо StringBuilder.delete ():
источник
Самый быстрый способ - использовать "setLength". Это не будет связано с операцией копирования. Способ создания нового StringBuilder должен быть полностью исключен . Замедление для StringBuilder.delete (int start, int end) связано с тем, что он снова скопирует массив для части изменения размера.
После этого StringBuilder.delete () обновит StringBuilder.count до нового размера. В то время как StringBuilder.setLength () просто упрощает обновление StringBuilder.count до нового размера.
источник
Первый лучше для людей. Если второй работает немного быстрее на некоторых версиях некоторых JVM, что с того?
Если производительность настолько критична, обойдите StringBuilder и напишите свой собственный. Если вы хороший программист и примете во внимание, как ваше приложение использует эту функцию, вы сможете сделать это еще быстрее. Стоит? Возможно нет.
Почему этот вопрос обозначен как «любимый вопрос»? Потому что оптимизация производительности - это очень весело, независимо от того, практично это или нет.
источник
Я не думаю, что имеет смысл пытаться таким образом оптимизировать производительность. Сегодня (2019 г.) оба состояния работают около 11 секунд для 100 000 000 циклов на моем ноутбуке I5:
==> 11000 мс (объявление внутри цикла) и 8236 мс (объявление вне цикла)
Даже если я запускаю программы для дедупликации адресов с несколькими миллиардами циклов с разницей в 2 секунды. для 100 миллионов циклов не имеет никакого значения, потому что эти программы работают часами. Также имейте в виду, что все будет по-другому, если у вас есть только один оператор добавления:
==> 3416 мс (внутренний цикл), 3555 мс (внешний цикл) Первый оператор, создающий StringBuilder внутри цикла, в этом случае выполняется быстрее. И, если вы измените порядок выполнения, он будет намного быстрее:
==> 3638 мс (внешний цикл), 2908 мс (внутренний цикл)
С уважением, Ульрих
источник
Объявить один раз и каждый раз назначать. Это более прагматичная и многоразовая концепция, чем оптимизация.
источник