Почему конкатенация строк в цикле с помощью оператора `+` неэффективна в Go и какие есть альтернативы?

Ответ

Конкатенация строк через оператор + неэффективна из-за иммутабельности (неизменяемости) строк в Go. Каждая операция + создает совершенно новую строку в памяти, выделяя для нее новый участок и копируя туда содержимое объединяемых строк. В цикле это приводит к множественным аллокациям и копированиям, что сильно снижает производительность и создает нагрузку на сборщик мусора.

Пример неэффективного кода:

func inefficientConcat(words []string) string {
    var result string
    for _, word := range words {
        result += word + " " // На каждой итерации создается новая строка
    }
    return result
}

Эффективные альтернативы

Для решения этой проблемы существуют более производительные инструменты:

1. strings.Builder

Это самый предпочтительный и идиоматичный способ для построения строк из нескольких частей. strings.Builder использует внутренний байтовый срез ([]byte), который растет по мере необходимости, минимизируя количество аллокаций. Память выделяется только тогда, когда внутренний буфер переполняется, и новая аллокация происходит с запасом.

import "strings"

func efficientConcat(words []string) string {
    var builder strings.Builder
    // Можно заранее выделить память, если известен примерный размер
    // builder.Grow(1024)

    for _, word := range words {
        builder.WriteString(word)
        builder.WriteString(" ")
    }
    return builder.String() // Только здесь происходит финальное создание строки
}

2. strings.Join

Если у вас есть срез строк, которые нужно объединить через разделитель, strings.Join — это самый читаемый и эффективный вариант. Он сначала вычисляет итоговый размер строки, делает одну аллокацию, а затем копирует все части.

import "strings"

func joinExample(words []string) string {
    // Идеально для объединения слайса строк с разделителем
    return strings.Join(words, " ")
}

3. bytes.Buffer

Работает аналогично strings.Builder, но оперирует срезом байт. Исторически bytes.Buffer появился раньше. Сегодня strings.Builder является более предпочтительным для построения именно строк, так как его API более строгое и он может быть немного производительнее за счет внутренних оптимизаций.

Когда что использовать?

  • Простая конкатенация 2-3 строк вне цикла: Используйте оператор +. Это читаемо и просто.
  • Построение строки из множества частей в цикле: Используйте strings.Builder.
  • Объединение среза строк с разделителем: Используйте strings.Join.