Разница между StringBuilder и StringBuffer
В чем основное отличие между StringBuffer
и StringBuilder
? Существуют ли проблемы с производительностью при выборе одного из этих классов?
5 ответ(ов)
Просто используйте StringBuilder
, если вам действительно не нужно делиться буфером между потоками. StringBuilder
– это несинхронизированная (меньше накладных расходов = более эффективная) версия оригинального синхронизированного класса StringBuffer
.
StringBuffer
появился первым. Sun заботилась о корректности во всех условиях, поэтому они сделали его синхронизированным, чтобы обеспечить безопасность при работе с потоками.
StringBuilder
был представлен позже. Большинство случаев использования StringBuffer
было в однопоточных приложениях, и это привело к ненужным затратам на синхронизацию.
Поскольку StringBuilder
является заменой для StringBuffer
, но без синхронизации, не будет никаких различий между примерами использования этих классов.
Если же вам действительно нужно делиться данными между потоками, вы можете использовать StringBuffer
, но подумайте, не будет ли целесообразнее использовать более высокоуровневую синхронизацию. Например, возможно, вместо использования StringBuffer
стоит синхронизировать методы, использующие StringBuilder
.
Сначала давайте рассмотрим сходства: Как StringBuilder, так и StringBuffer являются изменяемыми объектами. Это означает, что вы можете изменять их содержимое в том же месте.
Различия: StringBuffer является изменяемым и синхронизированным, в то время как StringBuilder изменяемый, но по умолчанию не синхронизирован.
Что значит синхронизированный (синхронизация): Когда что-то является синхронизированным, это значит, что несколько потоков могут одновременно получать доступ и изменять его без каких-либо проблем или побочных эффектов. StringBuffer синхронизирован, поэтому вы можете использовать его с несколькими потоками без каких-либо проблем.
Когда использовать что? StringBuilder: Когда вам нужна изменяемая строка, и только один поток имеет доступ к ней и модифицирует её. StringBuffer: Когда вам нужна изменяемая строка, и несколько потоков получают доступ к ней и модифицируют её.
Примечание: Не используйте StringBuffer без необходимости, то есть, не используйте его, если только один поток изменяет и получает доступ к строке, поскольку он содержит много кода для блокировки и разблокировки для синхронизации, что ненужно расходует ресурсы ЦП. Не используйте блокировки, если это не требуется.
В однопоточных приложениях StringBuffer не значительно медленнее, чем StringBuilder, благодаря оптимизациям JVM. Но в многопоточном режиме безопасно использовать только StringBuffer.
Вот мой тест (это не бенчмарк, просто тест):
public static void main(String[] args) {
String withString = "";
long t0 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
withString += "some string";
}
System.out.println("strings: " + (System.currentTimeMillis() - t0));
t0 = System.currentTimeMillis();
StringBuffer buf = new StringBuffer();
for (int i = 0; i < 100000; i++) {
buf.append("some string");
}
System.out.println("Buffers: " + (System.currentTimeMillis() - t0));
t0 = System.currentTimeMillis();
StringBuilder building = new StringBuilder();
for (int i = 0; i < 100000; i++) {
building.append("some string");
}
System.out.println("Builder: " + (System.currentTimeMillis() - t0));
}
Результаты:
- strings: 319740
- Buffers: 23
- Builder: 7!
Таким образом, StringBuilder быстрее StringBuffer и значительно быстрее, чем конкатенация строк. Теперь давайте используем Executor для многопоточности:
public class StringsPerf {
public static void main(String[] args) {
ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
// С использованием StringBuffer
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 10; i++) {
executorService.execute(new AppendableRunnable(buffer));
}
shutdownAndAwaitTermination(executorService);
System.out.println(" Thread Buffer: " + AppendableRunnable.time);
// С использованием StringBuilder
AppendableRunnable.time = 0;
executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10; i++) {
executorService.execute(new AppendableRunnable(builder));
}
shutdownAndAwaitTermination(executorService);
System.out.println(" Thread Builder: " + AppendableRunnable.time);
}
static void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // сокращенный код из официальной документации по Executor
try {
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (Exception e) {}
}
}
class AppendableRunnable<T extends Appendable> implements Runnable {
static long time = 0;
T appendable;
public AppendableRunnable(T appendable) {
this.appendable = appendable;
}
@Override
public void run() {
long t0 = System.currentTimeMillis();
for (int j = 0; j < 10000; j++) {
try {
appendable.append("some string");
} catch (IOException e) {}
}
time += (System.currentTimeMillis() - t0);
}
}
Теперь StringBuffer занимает 157 мс для 100000 добавлений. Это не тот же тест, но по сравнению с предыдущими 37 мс, можно безусловно утверждать, что StringBuffer медленнее в многопоточном режиме. Причина в том, что JIT/HotSpot компилятор проводит оптимизации, когда не требуется проверка блокировок.
Однако при использовании StringBuilder вы можете получить java.lang.ArrayIndexOutOfBoundsException, потому что параллельный поток пытается добавить что-то в недопустимое место.
Вывод: не стоит гнаться за StringBuffer. В многопоточных приложениях стоит подумать о том, что делают потоки, прежде чем пытаться выиграть несколько наносекунд.
Хороший вопрос!
Вот различия, которые я заметил:
StringBuffer:
StringBuffer
— синхронизирован.StringBuffer
— безопасен для потоков (thread-safe).StringBuffer
медленнее (попробуйте написать тестовую программу и выполнить её, время выполнения будет больше, чем уStringBuilder
).
StringBuilder:
StringBuilder
— не синхронизирован.StringBuilder
— не безопасен для потоков (not thread-safe).- Производительность
StringBuilder
лучше, чем уStringBuffer
.
Общее:
Оба класса имеют одинаковые методы с одинаковыми сигнатурами. Оба являются изменяемыми (mutable).
**StringBuffer**
- Синхронизирован, поэтому потокобезопасен.
- Потокобезопасен, но из-за этого медленнее.
**StringBuilder**
- Введён в Java 5.0.
- Асинхронен, следовательно, быстрее и эффективнее.
- Пользователю нужно явно синхронизировать его, если это необходимо.
- Его можно заменить на `StringBuffer` без каких-либо других изменений.
StringBuilder против конкатенации строк в toString() в Java
Как работают сервлеты? Инстанцирование, сессии, общие переменные и многопоточность
Какова разница между JDK и JRE?
Возможные значения конфигурации hbm2ddl.auto в Hibernate и их назначение
Что такое PECS (Producer Extends Consumer Super)?