StringBuilder и StringBuffer в Java - Вопросы
Всего: 6 вопросов
1. Чем отличаются String, StringBuilder и StringBuffer в Java?
Чем отличаются String, StringBuilder и StringBuffer в Java?
String неизменяем: любая «модификация» создаёт новый объект. StringBuilder и StringBuffer изменяемы — методы append(), insert(), delete(), replace(), reverse() меняют содержимое того же объекта.
Разница между StringBuilder и StringBuffer одна: методы StringBuffer синхронизированы (потокобезопасны), а StringBuilder — нет. Поэтому в однопоточном коде StringBuilder быстрее и используется по умолчанию. StringBuffer появился в Java 1.0, StringBuilder — в Java 5.
Все три класса объявлены как final, реализуют CharSequence и находятся в пакете java.lang, поэтому import не нужен. String сравнивается через equals() по содержимому, а StringBuilder и StringBuffer — по ссылке.
2. Почему конкатенация через += в цикле медленнее StringBuilder и нужен ли StringBuilder для одного выражения вроде "Привет, " + name?
Почему конкатенация через += в цикле медленнее StringBuilder и нужен ли StringBuilder для одного выражения вроде "Привет, " + name?
Оператор s += part в цикле на каждой итерации создаёт новый String и копирует в него весь накопленный текст — при n итерациях копируется порядка n²/2 символов. StringBuilder дописывает символы в один и тот же буфер, поэтому работа растёт линейно:
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append(i);
}
System.out.println(sb); // 01234
Для одиночного выражения явный StringBuilder не нужен: до Java 8 включительно компилятор сам превращал + в цепочку append(), а с Java 9 (JEP 280) использует invokedynamic и StringConcatFactory. Поэтому IntelliJ IDEA предлагает инспекцию «StringBuilder can be replaced with String». В цикле эта оптимизация не помогает, а объект StringBuilder нужно создавать один раз до цикла, а не внутри него.
3. Чем length() отличается от capacity() у StringBuilder и как растёт внутренний буфер?
Чем length() отличается от capacity() у StringBuilder и как растёт внутренний буфер?
length() возвращает количество символов в строке сейчас, capacity() — размер внутреннего буфера, который всегда не меньше длины. Метода size() у StringBuilder нет.
Начальная ёмкость зависит от конструктора: new StringBuilder() — 16, new StringBuilder(int capacity) — заданное число, new StringBuilder(String str) — str.length() + 16. Например, для "Hello" length = 5, capacity = 21.
При переполнении буфер растёт примерно вдвое (старая capacity * 2 + 2, но не меньше требуемого): пустой builder после добавления 17 символов получит capacity 34. Если итоговый размер известен заранее, передайте его в конструктор, а лишний запас можно освободить методом trimToSize(). Ловушка: new StringBuilder('a') вызывает конструктор с int — создаётся пустой объект с capacity 97.
4. Как правильно сравнить два StringBuilder по содержимому?
Как правильно сравнить два StringBuilder по содержимому?
StringBuilder и StringBuffer не переопределяют equals() и hashCode(), поэтому sb1.equals(sb2) сравнивает ссылки и вернёт false даже для одинакового текста. Правильные способы:
sb1.toString().equals(sb2.toString()); // true
sb1.compareTo(sb2) == 0; // true, Java 11+
"Hello".contentEquals(sb1); // String vs StringBuilder
Начиная с Java 11 StringBuilder и StringBuffer реализуют Comparable, поэтому их можно сравнивать через compareTo() и сортировать. По той же причине (нет equals()/hashCode() по содержимому) StringBuilder нельзя использовать как ключ HashMap или элемент HashSet — кладите туда sb.toString().
5. Делает ли синхронизация StringBuffer потокобезопасной любую последовательность вызовов?
Делает ли синхронизация StringBuffer потокобезопасной любую последовательность вызовов?
Нет. Методы StringBuffer объявлены synchronized, но это защищает только отдельный вызов. Составная операция из двух потоков всё равно может дать гонку:
if (sb.length() > 0) sb.append(", ");
Между проверкой length() и вызовом append() другой поток успеет изменить буфер. Для таких операций нужна внешняя синхронизация, а чаще проще дать каждому потоку свой StringBuilder. StringBuilder безопасен и в многопоточном приложении, если объект живёт внутри одного метода или потока; StringBuffer нужен, только когда один объект одновременно меняют несколько потоков. Он не помечен как @Deprecated, но в новом коде встречается редко.
6. Как добавить текст в начало StringBuilder и как повторить строку несколько раз?
Как добавить текст в начало StringBuilder и как повторить строку несколько раз?
Метода prepend() в Java нет: добавление в начало делается через insert(0, value). Допустимый offset у insert() — от 0 до length() включительно, иначе будет StringIndexOutOfBoundsException.
StringBuilder sb = new StringBuilder("I like Java!");
sb.insert(0, ">> ");
System.out.println(sb); // >> I like Java!
System.out.println(new StringBuilder().repeat("=", 10)); // ========== (Java 21+)
Многократный insert(0, ...) в цикле сдвигает все символы и работает так же медленно, как +=: лучше добавлять через append() и один раз вызвать reverse() или собрать части в другом порядке. Метод repeat(str, count) появился в Java 21 и добавляет строку или символ count раз; при отрицательном count выбрасывается IllegalArgumentException.