Перейти к содержимому
Теория · Строки · урок 7 из 11

StringBuilder

Строку изменить нельзя. А если текст нужно собирать по кусочку, например в цикле? Каждый + создаёт новую строку:

JAVA
public class Main {
    public static void main(String[] args) {
        String s = "";
        for (int i = 0; i < 5; i++) {
            s = s + i;
        }
        System.out.println(s); // 01234
    }
}

Программа работает и печатает 01234. Но посмотрим, что происходит на каждом круге цикла:

  1. На первом круге s + i создаёт новую строку 0.
  2. На втором — новую строку 01: в неё копируется старый символ и дописывается новый.
  3. На третьем — строку 012, и снова копируются все прежние символы.
  4. И так до конца: каждый круг делает новую карточку и переписывает на неё весь накопленный текст.

Если каждый круг добавляет один символ, то на сотом круге Java переписывает 100 символов, а на десятитысячном — 10 000. За 10 000 кругов набегает 1 + 2 + … + 10 000, то есть около 50 миллионов символов, хотя в готовой строке их всего 10 000. Чем длиннее текст, тем дороже каждый следующий круг. Почему + в цикле работает медленно — частый вопрос на собеседовании.

Представьте черновик. На карточке ничего не исправишь, а на черновике можно дописывать, вставлять и стирать сколько угодно. Когда текст готов, его переписывают начисто на карточку. В Java такой черновик называется StringBuilder.

Черновик StringBuilder

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

JAVA
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
    sb.append(i);
}
String result = sb.toString();
System.out.println(result); // 01234

Разберём по строкам:

  1. new StringBuilder() создаёт пустой черновик. Слово new создаёт новый объект.
  2. sb.append(i) дописывает число в конец черновика. Новая строка при этом не создаётся.
  3. sb.toString() переписывает текст начисто: возвращает обычную строку String.
  4. println(result) печатает 01234.

Когда текст перестаёт помещаться, черновик сам берёт себе место с запасом. Поэтому обычно круг дописывает только новый символ, а весь текст переносится изредка — когда черновик растёт.

Здесь картинка с черновиком неточна. Бумажный черновик после переписывания начисто обычно выбрасывают. StringBuilder после toString() никуда не девается: его можно менять дальше, а уже готовая строка от этого не изменится.

Методы черновика

JAVA
StringBuilder sb = new StringBuilder("Java");
sb.append("!").insert(0, "Hi, ");
System.out.println(sb);          // Hi, Java!
System.out.println(sb.length()); // 9
sb.reverse();
System.out.println(sb);          // !avaJ ,iH

Разберём методы по одному:

  • append("!") дописывает значение в конец: Java!;
  • insert(0, "Hi, ") вставляет текст перед индексом 0, то есть в начало: Hi, Java!;
  • length() возвращает длину, как у строки;
  • reverse() переворачивает текст задом наперёд.

Обратите внимание на две вещи:

  • append, insert и reverse возвращают тот же черновик. Поэтому вызовы можно писать цепочкой: в sb.append("!").insert(0, "Hi, ") сначала выполняется append, потом insert у того же sb. Цепочка выполняется слева направо.
  • sb.reverse(); меняет sb без всякого присваивания. Метод строки вернул бы новую строку, а метод StringBuilder меняет сам черновик.

println(sb) печатает текст черновика: println сам вызывает у него toString().

StringBuilder или +

Когда что выбирать:

  • склейку в одном выражении, например "Hi, " + name + "!", оставьте на +: Java собирает такую строку за один раз;
  • текст, который собирают в цикле, собирайте в StringBuilder.

На собеседовании к этому вопросу часто добавляют второй: чем StringBuilder отличается от StringBuffer. Методы у них одинаковые. Но StringBuffer защищён на случай, когда с ним одновременно работают несколько потоков — частей программы, которые выполняются параллельно. Эта защита стоит скорости, поэтому в обычном коде берут StringBuilder.

Частая ошибка

Черновик положили в переменную типа String:

JAVA
StringBuilder sb = new StringBuilder("Hi");
sb.append("!");
String text = sb; // ✗
TEXT
error: incompatible types: StringBuilder cannot be converted to String

Программа не компилируется: StringBuilder и String — разные типы, и черновик сам строкой не становится. Перепишите его начисто: String text = sb.toString();.

Главное

  • + в цикле каждый раз создаёт новую строку и копирует весь накопленный текст.
  • StringBuilder — изменяемый черновик: append, insert и reverse меняют его на месте и возвращают его же.
  • Вызовы методов StringBuilder можно писать цепочкой; они выполняются слева направо.
  • Готовую строку получают через toString(); в обычном коде берут StringBuilder, а не StringBuffer.