Скажите мне оперативную ситуацию для сравнения String
, StringBuffer
, и StringBuilder
?
Разница в изменчивости:
Строка
является неизменной , если вы попытаетесь изменить их значения, будет создан другой объект, тогда как StringBuffer
и StringBuilder
являются изменяемыми , поэтому они могут изменять свои значения.
Разница в безопасности потоков:
Разница между StringBuffer
и StringBuilder
заключается в том, что StringBuffer
является потокобезопасным. Поэтому, когда приложение нужно запускать только в одном потоке, лучше использовать StringBuilder
. StringBuilder
более эффективен, чем StringBuffer
.
Ситуации:
String
неизменяем. StringBuilder
будет достаточно хорошим. StringBuffer
, потому что StringBuffer
является синхронным, поэтому у вас есть потокобезопасность. Обратите внимание, что если вы используете Java 5 или новее, вы должны использовать StringBuilder
вместо StringBuffer
. Из документации API:
Начиная с выпуска JDK 5, этот класс был дополнен эквивалентным классом, предназначенным для использования в одном потоке,
StringBuilder
. КлассStringBuilder
обычно следует использовать вместо этого, поскольку он поддерживает все те же операции, но работает быстрее, так как не выполняет синхронизацию.
На практике вы почти никогда не будете использовать это из нескольких потоков одновременно, поэтому синхронизация, которую выполняет StringBuffer
, почти всегда является ненужными накладными расходами.
String
, когда уместна неизменяемая структура; получение новой последовательности символов из String
может привести к неприемлемому снижению производительности, как во времени процессора, так и в памяти (получение подстрок эффективно с точки зрения ЦП, потому что данные не копируются, но это означает потенциально гораздо больший объем данных может оставаться выделенным). StringBuilder
, когда вам нужно создать изменяемую последовательность символов, обычно для объединения нескольких последовательностей символов вместе. StringBuffer
в тех же обстоятельствах, что и StringBuilder
, но когда изменения в базовой строке должны быть синхронизированы (поскольку несколько потоков читают / модифицируют строковый буфер). См. Пример здесь .
Основы:
String
- неизменяемый класс, его нельзя изменить.
StringBuilder
- это изменяемый класс, который можно дополнять, заменять или удалять символы и в конечном итоге преобразовывать в String
StringBuffer
- это оригинальная синхронизированная версия StringBuilder
Вы должны предпочесть StringBuilder
во всех случаях, когда к вашему объекту имеет доступ только один поток.
Подробности:
Также обратите внимание, что StringBuilder/Buffers
не являются магическими, они просто используют массив в качестве объекта подложки, и этот массив должен быть перераспределен, когда он становится полным. Обязательно создавайте свои объекты StringBuilder/Buffer
изначально достаточно большими, чтобы их не приходилось постоянно менять размер при каждом вызове .append()
.
Изменение размера может стать очень вырожденным. По сути, при каждом расширении подкрепляющего массива его размер увеличивается в 2 раза. Это может привести к тому, что большое количество оперативной памяти будет выделено и не использовано, когда классы StringBuilder/Buffer
начнут расти в размерах.
В Java String x = "A" + "B";
использует StringBuilder
за кулисами. Поэтому для простых случаев нет никакой пользы от объявления своего собственного. Но если вы создаете String
объекты большого размера, скажем, менее 4k, то объявление StringBuilder sb = StringBuilder(4096);
гораздо эффективнее, чем конкатенация или использование конструктора по умолчанию, который содержит только 16 символов. Если ваша String
будет меньше 10k, то инициализируйте ее конструктором на 10k, чтобы быть в безопасности. Но если инициализировать ее на 10k, а затем записать на 1 символ больше, чем 10k, она будет перераспределена и скопирована в массив размером 20k. Поэтому лучше инициализировать большим числом, чем меньшим.
В случае с автоматическим изменением размера, на 17-м символе резервный массив перераспределяется и копируется в 32 символа, на 33-м символе это происходит снова, и вы получаете перераспределение и копирование массива в 64 символа. Вы можете видеть, как это вырождается в множество повторных выделений и копий, чего вы на самом деле пытаетесь избежать, используя StringBuilder/Buffer
в первую очередь.
Это из исходного кода JDK 6 для AbstractStringBuilder
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2;
if (newCapacity < 0) {
newCapacity = Integer.MAX_VALUE;
} else if (minimumCapacity > newCapacity) {
newCapacity = minimumCapacity;
}
value = Arrays.copyOf(value, newCapacity);
}
Лучшая практика - инициализировать StringBuilder/Buffer
немного больше, чем вы думаете, что вам понадобится, если вы не знаете, насколько большой будет String
, но можете предположить. Одно выделение немного большего объема памяти, чем вам нужно, будет лучше, чем множество повторных выделений и копий.
Также остерегайтесь инициализировать StringBuilder/Buffer
с String
, поскольку в этом случае будет выделен только размер String + 16 символов, что в большинстве случаев просто запустит вырожденный цикл перераспределения и копирования, которого вы пытаетесь избежать. Следующее взято прямо из исходного кода Java 6.
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
Если вы случайно окажетесь с экземпляром StringBuilder/Buffer
, который вы не создавали и не можете контролировать вызываемый конструктор, есть способ избежать вырожденного поведения повторного выделения и копирования. Вызовите .ensureCapacity()
с размером, который вы хотите гарантировать, что ваша результирующая String
поместится в него.
Альтернативы:
Просто в качестве примечания, если вы делаете действительно тяжелые String
построения и манипуляции, существует гораздо более ориентированная на производительность альтернатива под названием Ropes.
Другая альтернатива - создать реализацию StringList
путем подклассификации ArrayList
, добавить счетчики для отслеживания количества символов в каждом .append()
и других операциях мутации списка, затем переопределить . toString()
для создания StringBuilder
точного размера, который вам нужен, пройдитесь по списку и постройте вывод, вы даже можете сделать этот StringBuilder
переменной экземпляра и "кэшировать" результаты .toString()
и генерировать его заново только когда что-то изменится.
Также не забывайте о String.format()
при построении вывода с фиксированным форматом, который может быть оптимизирован компилятором по мере его улучшения.
Кроме того, StringBuffer
является потокобезопасным, а StringBuilder
- нет.
Таким образом, в ситуации реального времени, когда к нему обращаются разные потоки, StringBuilder
может иметь неопределенный результат.
Вы имеете в виду, для конкатенации?
Реальный пример: Вы хотите создать новую строку из многих других.
Например, для отправки сообщения:
String
String s = "Dear " + user.name + "<br>" +
" I saw your profile and got interested in you.<br>" +
" I'm " + user.age + "yrs. old too"
StringBuilder
String s = new StringBuilder().append.("Dear ").append( user.name ).append( "<br>" )
.append(" I saw your profile and got interested in you.<br>")
.append(" I'm " ).append( user.age ).append( "yrs. old too")
.toString()
Или
String s = new StringBuilder(100).appe..... etc. ...
// The difference is a size of 100 will be allocated upfront as fuzzy lollipop points out.
StringBuffer (синтаксис точно такой же, как и у StringBuilder, но эффекты разные)
About
StringBuffer
vs. StringBuilder
Первый синхронизируется, а второй нет.
Поэтому, если вы вызовете его несколько раз в одном потоке (а это 90% случаев), StringBuilder
будет работать намного быстрее, потому что он не будет останавливаться, чтобы проверить, владеет ли он блокировкой потока.
Поэтому рекомендуется использовать StringBuilder
(если, конечно, у вас нет одновременного доступа к нему более чем одного потока, что бывает редко). Конкатенация
String
( с использованием оператора + ) может быть оптимизирована компилятором для использования StringBuilder
, поэтому больше не стоит беспокоиться об этом, в старые дни Java, это было то, чего все говорили, что нужно избегать любой ценой, потому что каждая конкатенация создавала новый объект String. Современные компиляторы больше не делают этого, но все же хорошей практикой является использование StringBuilder
вместо этого, на случай, если вы используете "старый" компилятор.
edit
Просто для любопытных, вот что делает компилятор для этого класса:
class StringConcatenation {
int x;
String literal = "Value is" + x;
String builder = new StringBuilder().append("Value is").append(x).toString();
}
javap -c StringConcatenation
Compiled from "StringConcatenation.java"
class StringConcatenation extends java.lang.Object{
int x;
java.lang.String literal;
java.lang.String builder;
StringConcatenation();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: new #2; //class java/lang/StringBuilder
8: dup
9: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
12: ldc #4; //String Value is
14: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_0
18: getfield #6; //Field x:I
21: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
24: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: putfield #9; //Field literal:Ljava/lang/String;
30: aload_0
31: new #2; //class java/lang/StringBuilder
34: dup
35: invokespecial #3; //Method java/lang/StringBuilder."<init>":()V
38: ldc #4; //String Value is
40: invokevirtual #5; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
43: aload_0
44: getfield #6; //Field x:I
47: invokevirtual #7; //Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
50: invokevirtual #8; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
53: putfield #10; //Field builder:Ljava/lang/String;
56: return
}
Строки под номерами 5-27 предназначены для строки с именем "literal"
Строки под номерами 31-53 предназначены для строки с именем "builder"
Разницы нет, для обеих строк выполняется точно такой же одинаковый код.
Лично я не думаю, что есть какое-либо реальное применение StringBuffer
. Когда я захочу общаться между несколькими потоками, манипулируя последовательностью символов? Это звучит совсем не полезно, но, возможно, я еще не прозрел :)