Строка, StringBuffer и StringBuilder

Скажите мне оперативную ситуацию для сравнения String, StringBuffer, и StringBuilder?

206
задан Radiodef 27 August 2018 в 07:03
поделиться

7 ответов

Разница в изменчивости:

Строка является неизменной , если вы попытаетесь изменить их значения, будет создан другой объект, тогда как StringBuffer и StringBuilder являются изменяемыми , поэтому они могут изменять свои значения.

Разница в безопасности потоков:

Разница между StringBuffer и StringBuilder заключается в том, что StringBuffer является потокобезопасным. Поэтому, когда приложение нужно запускать только в одном потоке, лучше использовать StringBuilder . StringBuilder более эффективен, чем StringBuffer .

Ситуации:

  • Если ваша строка не будет изменяться, используйте класс String, потому что объект String неизменяем.
  • Если ваша строка может изменяться (пример: множество логических операций и операций при построении строки) и будет доступна только из одного потока, использование StringBuilder будет достаточно хорошим.
  • Если ваша строка может изменяться и к ней будут обращаться из нескольких потоков, используйте StringBuffer , потому что StringBuffer является синхронным, поэтому у вас есть потокобезопасность.
369
ответ дан 23 November 2019 в 04:46
поделиться

Обратите внимание, что если вы используете Java 5 или новее, вы должны использовать StringBuilder вместо StringBuffer . Из документации API:

Начиная с выпуска JDK 5, этот класс был дополнен эквивалентным классом, предназначенным для использования в одном потоке, StringBuilder . Класс StringBuilder обычно следует использовать вместо этого, поскольку он поддерживает все те же операции, но работает быстрее, так как не выполняет синхронизацию.

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

3
ответ дан 23 November 2019 в 04:46
поделиться
  • Вы используете String , когда уместна неизменяемая структура; получение новой последовательности символов из String может привести к неприемлемому снижению производительности, как во времени процессора, так и в памяти (получение подстрок эффективно с точки зрения ЦП, потому что данные не копируются, но это означает потенциально гораздо больший объем данных может оставаться выделенным).
  • Вы используете StringBuilder , когда вам нужно создать изменяемую последовательность символов, обычно для объединения нескольких последовательностей символов вместе.
  • Вы используете StringBuffer в тех же обстоятельствах, что и StringBuilder , но когда изменения в базовой строке должны быть синхронизированы (поскольку несколько потоков читают / модифицируют строковый буфер).

См. Пример здесь .

47
ответ дан 23 November 2019 в 04:46
поделиться

Основы:

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() при построении вывода с фиксированным форматом, который может быть оптимизирован компилятором по мере его улучшения.

28
ответ дан 23 November 2019 в 04:46
поделиться

Кроме того, StringBuffer является потокобезопасным, а StringBuilder - нет.

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

4
ответ дан 23 November 2019 в 04:46
поделиться

Вы имеете в виду, для конкатенации?

Реальный пример: Вы хотите создать новую строку из многих других.

Например, для отправки сообщения:

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"

Разницы нет, для обеих строк выполняется точно такой же одинаковый код.

9
ответ дан 23 November 2019 в 04:46
поделиться

Лично я не думаю, что есть какое-либо реальное применение StringBuffer. Когда я захочу общаться между несколькими потоками, манипулируя последовательностью символов? Это звучит совсем не полезно, но, возможно, я еще не прозрел :)

.
3
ответ дан 23 November 2019 в 04:46
поделиться
Другие вопросы по тегам:

Похожие вопросы: