Сравниваем String, StringBuilder и StringBuffer

Узнайте о ключевых различиях между StringBuilder и StringBuffer в Java. Узнайте об их производительности и потокобезопасности, чтобы выбрать лучший вариант.

· 4 минуты на чтение

Для работы со строками в Java есть три основных класса: String, StringBuilder и StringBuffer. Давайте разберемся, какой класс когда использовать.

Быстрый и простой ответ

StringBuffer – потокобезопасный, а StringBuilder нет.

Спонсор поста

String

Класс String представляет собой символьную строку. Этот класс объявлен, как final, а значит у этого класса не может быть наследников.

Реализация класса сохраняет строку в виде неизменяемого массива байт. Таким образом объект класса String нельзя изменить, можно только создать новый.

У этого есть плюсы:

  • Благодаря неизменности, хэшкод экземпляра класса String кэшируется. Его не нужно вычислять каждый раз, потому что значения полей объекта никогда не изменятся после его создания. Это дает высокую производительность при использовании данного класса в качестве ключа для HashMap.
  • Класс String можно использовать в многопоточной среде без дополнительной синхронизации.
  • Еще одна особенность класса String — для него перегружен оператор "+" в Java. Поэтому конкатенация строк выполняется довольно просто.

Строковые литералы

У класса String есть еще одна особенность. Все строковые литералы, определенные в Java коде, вроде "asdf", на этапе компиляции кэшируются и добавляются в так называемый пул строк.

Однако не обошлось и без проблем. Со строками происходит множество действий: конкатенация, перевод в верхний/нижний регистр, извлечени подстроки, удаление символов в строке и так далее.

И так как строки не изменяются, то для каждой такой операции создается новый объект в куче. Создание объекта в куче требует времени, а также это приводит к тому, что Garbage Collector нужно будет находить и удалять строки. Все это сказывается на производительности приложения при работе со строками.

Как же избежать нерационального использования памяти?

Рандомный блок

StringBuilder

Классы StringBuilder и StringBuffer во многом похожи. Оба класса являются мутабельными, то есть содержимое строки может быть изменено после ее создания.

Они даже наследуются от одного класса AbstractStringBuilder.

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

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("world!");
String finalString = sb.toString();

Пример использования

Зачем же тогда два разных класса?

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

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

StringBuffer

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

Потокобезопасность

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

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

Таким образом StringBuffer стоит использовать в приложениях с многопоточной обработкой, где объект данного класса может меняться в различных потоках.

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

Ээээксперементы

Проведем замеры, чтобы посмотреть насколько использование StringBuilder и StringBuffer эффективнее.

Наш тест будет довольно простым:

  1. Замеряем время старта.
  2. Берем строку и добавляем к ней рандомную строку.
  3. Повторяем 1_000_000 раз.
  4. Замеряем время окончания.

Код для демо

package dev.struchkov.workshop.six.strings;

import org.apache.commons.lang3.RandomStringUtils;

public class Main {

    public static void main(String[] args) {
        long stringTime = string();
        long stringBufferTime = stringBuffer();
        long stringBuilder = stringBuilder();
        System.out.println();
        System.out.println("String: " + stringTime);
        System.out.println("StringBuffer: " + stringBufferTime);
        System.out.println("StringBuilder: " + stringBuilder);
    }

    private static long string() {
        final long startTime = System.currentTimeMillis();
        String string = "Test";
        for (int i = 0; i < 1_000_000; i++) {
            string += RandomStringUtils.randomAlphabetic(10);
        }
        final long finishTime = System.currentTimeMillis();
        return finishTime - startTime;
    }

    private static long stringBuffer () {
        final long startTime = System.currentTimeMillis();
        StringBuffer string = new StringBuffer("Test");
        for (int i = 0; i < 1_000_000; i++) {
            string.append(RandomStringUtils.randomAlphabetic(10));
        }
        final long finishTime = System.currentTimeMillis();
        return finishTime - startTime;
    }

    private static long stringBuilder() {
        final long startTime = System.currentTimeMillis();
        StringBuilder string = new StringBuilder("Test");
        for (int i = 0; i < 1_000_000; i++) {
            string.append(RandomStringUtils.randomAlphabetic(10));
        }
        final long finishTime = System.currentTimeMillis();
        return finishTime - startTime;
    }

}
Время измерялось в милисекундах

Результаты:

  • String совсем печальные: 3,6 минуты
  • StringBuilder: 236 миллисекнд.
  • StringBuffer: 239 миллисекунд.

Разница на лицо.

Резюмирую

В заключение, основное различие между StringBuilder и StringBuffer в Java заключается в том, что StringBuffer является потокобезопасным и синхронизированным, а StringBuilder - нет.

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

Struchkov Mark
Struchkov Mark
Задавайте вопросы, если что-то осталось не понятным👇