Проанализируйте этот пример некорректной оптимизации в Java — почему он медленный, какие затраты возникают и как правильно переписать с точки зрения алгоритма и API Java: String s = ""; for (String part : parts) s += part;
Коротко — потому что String в Java неизменяемый, и выражение s += part в цикле порождает много временных объектов и копирований символов. Итог — квадратичная по объёму данных работа и большой «мусор» для сборщика.
Детали и затраты
String неизменяем: чтобы «добавить» текст, нужно создать новый String.Оператор s += part при компиляции/выполнении обычно превращается в создание временного буфера (обычно StringBuilder или аналог через StringConcatFactory), вызов append и затем toString() — т.е. на каждую итерацию создаётся по крайней мере один временный буфер и один новый String.Каждое toString() приводит к копированию уже накопленных символов в новый массив символов (или байт в compact-strings), поэтому содержимое копируется многократно.Если parts содержит k частей общей длины N, то суммарный объём копий символов ~ O(N * k) в худшем/типичном случае (в практическом смысле — квадратичная зависимость по числу частей), тогда как оптимальный алгоритм — O(N).Итог: лишние выделения памяти, копирования, и повышенная нагрузка на GC; медленнее при большом объёме или большом числе частей.
Как правильно (алгоритмически и с API Java) 1) Лучший вариант — собрать в StringBuilder и один раз получить String:
Если можно заранее оценить итоговую длину — задайте начальную емкость, чтобы избежать повторных расширений. Пример: int totalLen = 0; for (String p : parts) totalLen += p.length(); StringBuilder sb = new StringBuilder(totalLen); for (String p : parts) sb.append(p); String s = sb.toString(); Это O(N) по времени, один итоговый массив символов.
2) Если parts — коллекция/массив и вы используете Java 8+ — используйте готовые API:
String s = String.join("", parts);или через стрим: String s = parts.stream().collect(Collectors.joining()); Они реализованы эффективно (через StringJoiner / StringBuilder) и проще по коду.
3) Не используйте StringBuffer (он синхронизирован и медленнее), если не нужна потокобезопасность.
Дополнительно
Не пытайтесь «оптимизировать» s += part вручную — это анти-паттерн для накопления строк.Для бенчмарка используйте JMH, а не System.currentTimeMillis() — результаты чувствительны к JIT/GC.
Краткое резюме Исходный код: String s = ""; for (String part : parts) s += part; плох потому, что создаёт временные буферы и копирует накопленную строку на каждой итерации (много копий, квадратичная сложность). Правильно: StringBuilder (с предвыделением), или String.join / Collectors.joining — одно создание итоговой строки и линейная сложность.
Коротко — потому что String в Java неизменяемый, и выражение s += part в цикле порождает много временных объектов и копирований символов. Итог — квадратичная по объёму данных работа и большой «мусор» для сборщика.
Детали и затраты
String неизменяем: чтобы «добавить» текст, нужно создать новый String.Оператор s += part при компиляции/выполнении обычно превращается в создание временного буфера (обычно StringBuilder или аналог через StringConcatFactory), вызов append и затем toString() — т.е. на каждую итерацию создаётся по крайней мере один временный буфер и один новый String.Каждое toString() приводит к копированию уже накопленных символов в новый массив символов (или байт в compact-strings), поэтому содержимое копируется многократно.Если parts содержит k частей общей длины N, то суммарный объём копий символов ~ O(N * k) в худшем/типичном случае (в практическом смысле — квадратичная зависимость по числу частей), тогда как оптимальный алгоритм — O(N).Итог: лишние выделения памяти, копирования, и повышенная нагрузка на GC; медленнее при большом объёме или большом числе частей.Как правильно (алгоритмически и с API Java)
Если можно заранее оценить итоговую длину — задайте начальную емкость, чтобы избежать повторных расширений.1) Лучший вариант — собрать в StringBuilder и один раз получить String:
Пример:
int totalLen = 0;
for (String p : parts) totalLen += p.length();
StringBuilder sb = new StringBuilder(totalLen);
for (String p : parts) sb.append(p);
String s = sb.toString();
Это O(N) по времени, один итоговый массив символов.
2) Если parts — коллекция/массив и вы используете Java 8+ — используйте готовые API:
String s = String.join("", parts);или через стрим: String s = parts.stream().collect(Collectors.joining());Они реализованы эффективно (через StringJoiner / StringBuilder) и проще по коду.
3) Не используйте StringBuffer (он синхронизирован и медленнее), если не нужна потокобезопасность.
Дополнительно
Не пытайтесь «оптимизировать» s += part вручную — это анти-паттерн для накопления строк.Для бенчмарка используйте JMH, а не System.currentTimeMillis() — результаты чувствительны к JIT/GC.Краткое резюме
Исходный код:
String s = "";
for (String part : parts)
s += part;
плох потому, что создаёт временные буферы и копирует накопленную строку на каждой итерации (много копий, квадратичная сложность). Правильно: StringBuilder (с предвыделением), или String.join / Collectors.joining — одно создание итоговой строки и линейная сложность.