Для работы со строками в 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
быстрее StringBuffer
в однопоточных приложениях, так как не имеет встроенных механизмов синхронизации. Однако это также означает, что он не является потокобезопасным и не должен использоваться в многопоточных средах, если не приняты дополнительные меры по синхронизации.
С точки зрения производительности, StringBuilder
обычно быстрее StringBuffer
, поскольку не имеет накладных расходов на синхронизацию. Однако в большинстве приложений разница в скорости обычно незначительна, и выбор между этими двумя классами должен быть основан на том, является ли безопасность потоков проблемой.
StringBuffer
StringBuffer
практически двойник класса StringBuilder
: одни и те же методы, которые одинаково используются. Единственное их различие состоит в том, что класс StringBuffer
синхронизированный и потокобезопасный.
Это означает, что если несколько потоков одновременно обращаются к одному и тому же объекту StringBuffer
, они не будут мешать друг другу. С другой стороны, если несколько потоков одновременно обращаются к одному и тому же объекту StringBuilder
, существует риск повреждения данных и других проблем.
Это делает StringBuffer
медленнее StringBuilder
в однопоточных приложениях, но это необходимо в многопоточных средах для предотвращения условий гонки и других проблем параллелизма.
Таким образом StringBuffer
стоит использовать в приложениях с многопоточной обработкой, где объект данного класса может меняться в различных потоках.
Еще одно различие заключается в том, что StringBuffer
занимает немного больше памяти, чем StringBuilder
, поскольку включает дополнительные данные синхронизации. Однако в большинстве приложений эта разница достаточно мала, чтобы ею можно было пренебречь.
Ээээксперементы
Проведем замеры, чтобы посмотреть насколько использование StringBuilder
и StringBuffer
эффективнее.
Наш тест будет довольно простым:
- Замеряем время старта.
- Берем строку и добавляем к ней рандомную строку.
- Повторяем 1_000_000 раз.
- Замеряем время окончания.
Код для демо
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
- нет.
Выбор между этими двумя классами должен быть основан на том, является ли безопасность потоков проблемой, а также на конкретных требованиях к производительности приложения. Оба класса являются мощными инструментами для работы со строками и могут использоваться как взаимозаменяемые во многих случаях.