В чём проблема конкатенации строк в цикле на Java?

Ответ

Проблема конкатенации строк в цикле заключается в крайне низкой производительности из-за неизменяемости (immutability) класса String в Java. Каждая операция конкатенации (+ или .concat()) создаёт новый объект String, копируя содержимое предыдущих строк.

Проблемный код (антипаттерн):

String result = "";
for (int i = 0; i < 100_000; i++) {
    result += "some data " + i; // Создаётся новый String на каждой итерации!
}

Почему это медленно:

  1. На каждой итерации создаются временные объекты StringBuilder (для +) и новый String.
  2. Происходит полное копирование всех символов из result и новой строки в новый массив символов.
  3. Старые объекты становятся мусором, увеличивая нагрузку на GC.

Решение: Использовать StringBuilder (или StringBuffer для многопоточности).

StringBuilder sb = new StringBuilder(); // Единый изменяемый буфер
for (int i = 0; i < 100_000; i++) {
    sb.append("some data ").append(i); // Добавление в существующий буфер
}
String result = sb.toString(); // Создание строки происходит только один раз

Ключевые отличия и best practices:

  • String: Используйте для фиксированных строк или однократной конкатенации.
  • StringBuilder: Используйте при множественных модификациях строки в одном потоке.
  • StringBuffer: Используйте при множественных модификациях в многопоточном окружении (синхронизированные методы).
  • Современные компиляторы могут оптимизировать простые случаи конкатенации вне циклов, но никогда не оптимизируют конкатенацию в цикле.

Вывод: Конкатенация в цикле создаёт O(n²) временных объектов. Всегда используйте StringBuilder для построения строк внутри циклов.

Ответ 18+ 🔞

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

Представь себе картину: ты пишешь красивый такой цикл, чтобы собрать, ну, там, логи или какой-нибудь длинный SQL-запрос. И делаешь ты это по-честному, по-простому:

String result = "";
for (int i = 0; i < 100_000; i++) {
    result += "some data " + i; // Ёбта, вот она, пизда!
}

И сидишь такой, довольный, запускаешь... А оно, сука, работает, будто через жопу ползёт! Удивление пиздец! А почему, спрашивается? А потому что String в Java — это как монашка, блядь, неизменяемая. Immutable, ёпта!

Что происходит на каждой итерации, когда ты пишешь result += "..."?

  1. Созётся новый, ёбаный, StringBuilder (неявно, компилятор такой хитрожопый).
  2. В него копируется ВЕСЬ текущий, уже накопленный, result. Представь, на 100-тысячной итерации ты тащишь за собой хвост в 99999 кусков "some data".
  3. Туда же добавляется новая строка.
  4. Из этого всего создаётся НОВЫЙ объект String.
  5. Старый result летит на свалку, к мудакам, и становится мусором.

И так 100 тысяч раз, Карл! Это ж пиздец какая операция O(n²), а не конкатенация! Ты не строку строишь, ты тут целый мусорный полигон разворачиваешь, и GC потом охуевает, пытаясь это всё убрать.

А как надо-то, блядь? Да элементарно, Винни!

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

StringBuilder sb = new StringBuilder(); // Создал один раз, и всё, блядь!
for (int i = 0; i < 100_000; i++) {
    sb.append("some data ").append(i); // Просто тычем новые куски в конец того же буфера
}
String result = sb.toString(); // И ТОЛЬКО ЗДЕСЬ создаётся итоговая строка. Один раз!

Вот теперь производительность будет O(n), потому что ты работаешь с одним изменяемым буфером символов. Никакого лишнего копирования, никакого овердохуища мусора. Красота!

Краткий гид по ебле:

  • String — для статичных текстов или разовой склейки пары строк. "Привет, " + username.
  • StringBuilder — для ВСЕГО, что собирается в цикле или через много вызовов .append() в одном потоке. Это твой рабочий инструмент.
  • StringBuffer — то же самое, но с синхронизацией, если на твою строку налетели пидары из нескольких потоков одновременно. Медленнее, но для многопоточки.

И да, не надейся, что компилятор тебя спасёт! Вне циклов — может, он там что-то соптимизирует. Но внутри цикла — никогда, блядь! Он не настолько умный, чтобы догадаться, что ты там 100 тысяч раз одно и то же делаешь.

Вывод, чувак: Запомни, как "Отче наш" — увидел конкатенацию в цикле, бей в глаза, тут же рефактори на StringBuilder. И твой код перестанет сосалкой быть.