← Назад к вопросам

В чем разница между конкатенацией и append в StringBuilder?

1.8 Middle🔥 161 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Разница между конкатенацией и append в StringBuilder

Оба способа объединяют строки, но имеют радикально разную производительность:

Краткая таблица

АспектКонкатенация (+)StringBuilder.append()
Синтаксисstr1 + str2sb.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 вместо конкатенации