
Конкатенация строк в Java: почему советы 2008 года всё ещё работают — и почему этого уже недостаточно
Snaret 7 минут назад Конкатенация строк в Java: почему советы 2008 года всё ещё работают — и почему этого уже недостаточно Уровень сложности Средний Время на прочтение 16 мин Охват и читатели 142 Java * for (String s :...
Anthropic — What company has the best second artificial intelligence model at the end of June?
В сфере искусственного интеллекта произошло заметное событие. Snaret 7 минут назад Конкатенация строк в Java: почему советы 2008 года всё ещё работают — и почему этого уже недостаточно Уровень сложности Средний Время на прочтение 16 мин Охват и читатели 142 Java * for (String s : data) { result += s; } Вы наверняка видели такой код сотни раз. Ведь он выглядит безобидно, почти идиоматично. Но в продакшене под нагрузкой этот цикл способен генерировать сотни мегабайт мусора в секунду - даже если сам результат никому не нужен.
И казалось бы, проблема конкатенации строк в Java давно решена. Джунам говорят: используй StringBuilder и будет тебе щастье. А статьи десятилетней давности сравнивают + и append() в бенчмарках и ставят точку.
Технические детали
В сегодняшней статье я копнул немного глубже и оказалось, что реальность сложнее. Вред не исчез - он принял новые, менее очевидные формы. Классика жива, но ее границы изменились Начнем с базы, без которой не понять остальное.
Есть те, кто до сих пор пишут так: String result = ""; for (String item : data) { result += item; } Компилятор честно разворачивает это в нечто похожее на: String result = ""; for (String item : data) { StringBuilder sb = new StringBuilder(); sb. append(item); result = sb. toString(); } Проблема этого кода в том, что каждая итерация создаёт новый StringBuilder , копирует в него всё накопленное содержимое и возвращает новую строку.
Количество операций копирования растёт квадратично относительно числа элементов: на 1000 итераций мы копируем не 1000 символов, а порядка 500 000. Разница между линейным и квадратичным ростом становится заметной очень быстро. Но даже явный StringBuilder не всегда спасает.
Отраслевые последствия
Сделаем так: StringBuilder sb = new StringBuilder(); for (String item : data) { sb. append(item); } String result = sb. toString(); И казалось бы, проблема решена.
Однако new StringBuilder() создает внутренний массив символов размером 16. Когда место заканчивается, массив пересоздается с удвоенным размером, и все накопленные байты копируются заново. Эти resize-ы происходят в цикле молча и по сути воспроизводят ту же проблему, но на уровень ниже - на уровне буфера.
Можно добавить new StringBuilder(estimatedSize) , с явно указанным размером массива и это уберет лишние копирования. Но важно другое: даже самый правильно нстроенный StringBuilder остаётся StringBuilder . В байткоде всё равно будет new StringBuilder , append , toString - жёсткая цепочка, которую JVM обязана исполнить.
Этот прогресс даёт важные сигналы о будущем отрасли, и технологический мир внимательно наблюдает.





