Перейти к основному содержимому

StringBuilder против StringBuffer в Java

· 3 мин. чтения

1. Обзор

В этой короткой статье мы рассмотрим сходства и различия между StringBuilder и StringBuffer в Java.

Проще говоря, StringBuilder появился в Java 1.5 как замена StringBuffer .

2. Сходства

И StringBuilder , и StringBuffer создают объекты, содержащие изменяемую последовательность символов. Давайте посмотрим, как это работает и как это соотносится с неизменяемым классом String :

String immutable = "abc";
immutable = immutable + "def";

Даже если может показаться, что мы изменяем один и тот же объект, добавляя «def» , мы создаем новый, потому что экземпляры String не могут быть изменены.

При использовании StringBuffer или StringBuilder мы можем использовать метод append() :

StringBuffer sb = new StringBuffer("abc");
sb.append("def");

В этом случае новый объект не создавался. Мы вызвали метод append() для экземпляра sb и изменили его содержимое. StringBuffer и StringBuilder — изменяемые объекты.

3. Отличия

StringBuffer синхронизирован и, следовательно, потокобезопасен. StringBuilder совместим с StringBuffer API, но без гарантии синхронизации.

Поскольку это не потокобезопасная реализация, она работает быстрее, и ее рекомендуется использовать там, где нет необходимости в потокобезопасности.

3.1. Производительность

На малых итерациях разница в производительности незначительна. Давайте сделаем быстрый микротест с JMH :

@State(Scope.Benchmark)
public static class MyState {
int iterations = 1000;
String initial = "abc";
String suffix = "def";
}

@Benchmark
public StringBuffer benchmarkStringBuffer(MyState state) {
StringBuffer stringBuffer = new StringBuffer(state.initial);
for (int i = 0; i < state.iterations; i++) {
stringBuffer.append(state.suffix);
}
return stringBuffer;
}

@Benchmark
public StringBuilder benchmarkStringBuilder(MyState state) {
StringBuilder stringBuilder = new StringBuilder(state.initial);
for (int i = 0; i < state.iterations; i++) {
stringBuilder.append(state.suffix);
}
return stringBuilder;
}

Мы использовали режим пропускной способности по умолчанию , т. е. количество операций в единицу времени (чем выше значение, тем лучше), что дает:

Benchmark                                          Mode  Cnt      Score      Error  Units
StringBufferStringBuilder.benchmarkStringBuffer thrpt 200 86169.834 ± 972.477 ops/s
StringBufferStringBuilder.benchmarkStringBuilder thrpt 200 91076.952 ± 2818.028 ops/s

Если мы увеличим количество итераций с 1k до 1m, то получим:

Benchmark                                          Mode  Cnt   Score   Error  Units
StringBufferStringBuilder.benchmarkStringBuffer thrpt 200 77.178 ± 0.898 ops/s
StringBufferStringBuilder.benchmarkStringBuilder thrpt 200 85.769 ± 1.966 ops/s

Однако давайте помнить, что это микротест, который может иметь или не иметь реального влияния на фактическую производительность приложения в реальном мире.

4. Выводы

Проще говоря, StringBuffer является потокобезопасной реализацией и, следовательно, медленнее, чем StringBuilder .

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

Наконец, как всегда, код, использованный во время обсуждения, можно найти на GitHub .