← Назад к вопросам
В чем разница между конкатенацией и append в StringBuilder?
1.8 Middle🔥 161 комментариев
#Основы Java
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Разница между конкатенацией и append в StringBuilder
Оба способа объединяют строки, но имеют радикально разную производительность:
Краткая таблица
| Аспект | Конкатенация (+) | StringBuilder.append() |
|---|---|---|
| Синтаксис | str1 + str2 | sb.append(str).append(str2) |
| Производительность | O(n²) в циклах | O(n) |
| Объекты в памяти | Новый объект каждый раз | Один буфер |
| Кодовая база | Меньше кода | Немного больше |
| Оптимизация | Компилятор частично оптимизирует | Явная оптимизация |
| Рекомендация | Для 1-2 объединений | Для множественных объединений |
| Потокобезопасность | Да (String immutable) | Нет (используй StringBuffer) |
Конкатенация строк с оператором +
Самый простой способ объединить строки.
Синтаксис:
String str1 = "Hello";
String str2 = " ";
String str3 = "World";
String result = str1 + str2 + str3; // "Hello World"
Что происходит в памяти:
Каждая операция + создаёт новый объект String:
String result = "a" + "b" + "c" + "d" + "e";
// Эквивалентно:
String result = new StringBuilder()
.append("a")
.append("b")
.append("c")
.append("d")
.append("e")
.toString();
Модерный Java-компилятор оптимизирует литеральную конкатенацию! Но в циклах проблема остаётся.
Проблема в циклах:
// ОЧЕНЬ ПЛОХО: O(n²) производительность!
String result = "";
for (int i = 0; i < 10000; i++) {
result = result + "x"; // Создаёт новый String на каждой итерации!
}
// Что происходит:
// Итерация 1: "" + "x" = "x" (1 char)
// Итерация 2: "x" + "x" = "xx" (2 chars)
// Итерация 3: "xx" + "x" = "xxx" (3 chars)
// ...
// Итерация 10000: "xxx..." + "x" (10000 chars)
//
// Всего: 1 + 2 + 3 + ... + 10000 = 50 млн операций!
Производительность конкатенации:
long start = System.nanoTime();
String result = "";
for (int i = 0; i < 100000; i++) {
result = result + "x";
}
long time = System.nanoTime() - start;
System.out.println("Time: " + time / 1000000 + "ms"); // ~5000ms (очень медленно!)
StringBuilder.append() - Правильный способ
Использует внутренний буфер для эффективного объединения.
Синтаксис:
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" ");
sb.append("World");
String result = sb.toString(); // "Hello World"
Как работает StringBuilder:
public class StringBuilder {
private char[] value; // Внутренний буфер
private int count; // Текущая длина
public StringBuilder append(String str) {
if (str == null) {
appendNull();
return this;
}
int len = str.length();
ensureCapacityInternal(count + len); // Увеличь буфер если нужно
str.getChars(0, len, value, count); // Скопируй символы
count += len;
return this; // Возвращаем this для chain!
}
private void ensureCapacityInternal(int minimumCapacity) {
if (minimumCapacity - value.length > 0) {
// Увеличь буфер (обычно в 2 раза)
expandCapacity(minimumCapacity);
}
}
}
Пример в цикле:
// ОТЛИЧНО: O(n) производительность!
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("x");
}
String result = sb.toString();
// Что происходит:
// 1. Создаём буфер (начальная ёмкость ~16)
// 2. Добавляем символы в буфер
// 3. Когда буфер переполняется, увеличиваем его в 2 раза
// 4. В конце вызываем toString() для создания String
//
// Всего: ~100000 операций (линейная сложность)!
Производительность StringBuilder:
long start = System.nanoTime();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("x");
}
String result = sb.toString();
long time = System.nanoTime() - start;
System.out.println("Time: " + time / 1000000 + "ms"); // ~5ms (очень быстро!)
Сравнение методов
Конкатенация - одна-две операции:
// Это нормально - компилятор оптимизирует
String greeting = "Hello" + " " + "World"; // O(1) - работает быстро
// С переменными тоже нормально
String name = "John";
String message = "Hello, " + name; // Быстро, нет проблем
// Но в цикле - проблема!
for (int i = 0; i < list.size(); i++) {
String line = "Item: " + list.get(i); // OK, создаёт 1 объект
// но если это в вложенном цикле - медленнее
}
StringBuilder - множественные объединения:
// Всегда быстро в любом контексте
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append(data[i]);
}
String result = sb.toString();
Практические примеры
Пример 1: Формирование SQL запроса
// ❌ НЕПРАВИЛЬНО: медленно
String query = "SELECT * FROM users";
for (String filter : filters) {
query = query + " AND " + filter; // Опасно в цикле!
}
// ✅ ПРАВИЛЬНО: быстро
StringBuilder queryBuilder = new StringBuilder("SELECT * FROM users");
for (String filter : filters) {
queryBuilder.append(" AND ").append(filter);
}
String query = queryBuilder.toString();
Пример 2: Формирование JSON вручную
// ❌ НЕПРАВИЛЬНО: конкатенация
String json = "{";
json = json + "\"name\":\"John\",";
json = json + "\"age\":30";
json = json + "}";
// ✅ ПРАВИЛЬНО: StringBuilder
StringBuilder json = new StringBuilder();
json.append("{");
json.append("\"name\":\"John\",");
json.append("\"age\":30");
json.append("}");
String result = json.toString();
// ✅ ЕЩЁ ЛУЧШЕ: используй Jackson или Gson
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
Пример 3: Чтение файла в одну строку
// ❌ НЕПРАВИЛЬНО: конкатенация в цикле
String content = "";
for (String line : Files.readAllLines(path)) {
content = content + line + "\\n"; // O(n²)!
}
// ✅ ПРАВИЛЬНО: StringBuilder
StringBuilder content = new StringBuilder();
for (String line : Files.readAllLines(path)) {
content.append(line).append("\\n");
}
String result = content.toString();
// ✅ ЛУЧШЕ: использовать готовые методы
String content = Files.readString(path); // Java 11+
StringBuffer vs StringBuilder
Оба похожи, но есть отличия:
// StringBuilder: НЕ потокобезопасен, но быстрее
// Используй в однопоточном коде
StringBuilder sb = new StringBuilder();
sb.append("thread").append("unsafe").append("but").append("fast");
// StringBuffer: потокобезопасен, медленнее
// Используй если работаешь с несколькими потоками
StringBuffer sb = new StringBuffer();
sb.append("thread").append("safe").append("but").append("slow");
// В 99% случаев используй StringBuilder
Конкатенация в методах
// ❌ ОПАСНО: конкатенация в параметрах
public void log(String prefix, String message) {
System.out.println(prefix + ": " + message); // Может быть в цикле!
}
// ✅ ЛУЧШЕ: используй параметризованные вызовы
public void log(String prefix, String message) {
System.out.printf("%s: %s%n", prefix, message);
}
// ✅ ЕЩЁ ЛУЧШЕ: используй логирование
logger.info("{}: {}", prefix, message); // SLF4J не строит строку если нет вывода!
Бенчмарк
// Конкатенация в цикле: 5000ms для 100K итераций
String result = "";
for (int i = 0; i < 100000; i++) {
result = result + "x"; // O(n²)
}
// StringBuilder: 5ms для 100K итераций
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("x"); // O(n)
}
String result = sb.toString();
// StringBuilder в 1000 раз БЫСТРЕЕ!
Итоговое резюме
Используй конкатенацию (+) когда:
- Объединяешь 1-2 строки
- В литеральных выражениях:
"Hello" + name - Компилятор оптимизирует автоматически
Используй StringBuilder когда:
- Множественные объединения
- В циклах
- Формирование больших строк
- Неизвестное количество операций
Золотое правило: если хотя бы раз вызываешь append в цикле → используй StringBuilder вместо конкатенации