strings.Builder: эффективная работа со строками
1. Проблема конкатенации
Задача: собрать строку из миллиона чисел
result := ""
for i := 1; i <= 1000000; i++ {
result += fmt.Sprintf("%d,", i)
}
Каждая итерация создаёт новую строку в памяти!
2. Измерение времени выполнения
start := time.Now()
// ... операции со строками ...
elapsed := time.Since(start)
fmt.Printf("Время: %v\n", elapsed)
fmt.Printf("Миллисекунд: %.3f ms\n", elapsed.Seconds()*1000)
time.Now() — засекаем время, time.Since() — вычисляем разницу.
3. Результаты тестирования конкатенации
| Итераций | Время | Комментарий |
|---|---|---|
| 1,000 | ~0 ms | Незаметно |
| 100,000 | ~1500 ms | 1.5 секунды |
| 1,000,000 | ~136,287 ms | 2 минуты 16 секунд! |
Вывод: Конкатенация через += катастрофически медленная.
4. Решение: strings.Builder
var sb strings.Builder
for i := 1; i <= 1000000; i++ {
sb.WriteString(fmt.Sprintf("%d,", i))
}
result := sb.String()
Инициализация не требуется — просто объявляем переменную.
5. Сравнение производительности
❌ Способ 1: Обычная конкатенация
start := time.Now()
result1 := ""
for i := 1; i <= 100000; i++ {
result1 += fmt.Sprintf("%d,", i) // Каждый раз новая строка!
}
elapsed1 := time.Since(start)
fmt.Printf("Время: %v\n", elapsed1)
fmt.Printf("Символов: %d\n", len(result1))
Результат: ~1500 ms (1.5 секунды).
✅ Способ 2: strings.Builder
start := time.Now()
var sb strings.Builder
for i := 1; i <= 100000; i++ {
sb.WriteString(fmt.Sprintf("%d,", i)) // Добавляет в буфер
}
result2 := sb.String() // Только в конце преобразуем
elapsed2 := time.Since(start)
fmt.Printf("Время: %v\n", elapsed2)
fmt.Printf("Символов: %d\n", len(result2))
Результат: ~70 ms.
6. Ускорение
Конкатенация: ~1500 ms
Builder: ~70 ms
Ускорение: 1500 / 70 ≈ 21× быстрее!
Для миллиона итераций: с 136 секунд (2 мин 16 сек) до ~700 ms.
7. Почему Builder быстрее?
Конкатенация через +=
Итерация 1: выделить память под "1,"
Итерация 2: выделить память под "1,2," (копировать "1,")
Итерация 3: выделить память под "1,2,3," (копировать "1,2,")
...
Итерация N: выделить N байт (копировать N-1)
strings.Builder
1. Выделить буфер с запасом
2. Добавлять данные в буфер (без копирования)
3. При нехватке места — увеличить буфер
4. В конце — один раз преобразовать в строку
8. Основные методы Builder
var sb strings.Builder
sb.WriteString("Hello") // Добавить строку
sb.WriteByte('!') // Добавить байт
sb.WriteRune('😀') // Добавить руну
result := sb.String() // Получить строку
length := sb.Len() // Текущая длина
sb.Reset() // Очистить буфер
9. Практические примеры
Построение SQL-запроса
var query strings.Builder
query.WriteString("SELECT * FROM users WHERE ")
conditions := []string{"age > 18", "country = 'US'", "active = true"}
for i, cond := range conditions {
if i > 0 {
query.WriteString(" AND ")
}
query.WriteString(cond)
}
sql := query.String()
// "SELECT * FROM users WHERE age > 18 AND country = 'US' AND active = true"
Форматирование логов
var log strings.Builder
log.WriteString("[INFO] ")
log.WriteString(time.Now().Format("2006-01-02 15:04:05"))
log.WriteString(" - User logged in: ")
log.WriteString("john_doe")
fmt.Println(log.String())
// [INFO] 2025-12-19 02:31:00 - User logged in: john_doe
Генерация HTML
var html strings.Builder
html.WriteString("<ul>\n")
items := []string{"Apple", "Banana", "Cherry"}
for _, item := range items {
html.WriteString(" <li>")
html.WriteString(item)
html.WriteString("</li>\n")
}
html.WriteString("</ul>")
fmt.Println(html.String())
Вывод:
<ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul>
10. Когда использовать Builder
| Сценарий | Используйте |
|---|---|
| 2-3 конкатенации | + (допустимо) |
| Цикл с конкатенацией | Builder (обязательно) |
| Динамическая сборка строк | Builder |
Множество += в коде |
Builder |
| Сотни/тысячи операций | Builder |
11. Оптимизация: предварительное выделение памяти
var sb strings.Builder
sb.Grow(1000000) // Зарезервировать 1 МБ
for i := 1; i <= 100000; i++ {
sb.WriteString(fmt.Sprintf("%d,", i))
}
result := sb.String()
Grow(n) — резервирует память, избегая многократных расширений буфера.
12. Сравнение с другими подходами
// ❌ Медленно
result := ""
for i := 0; i < 10000; i++ {
result += "x"
}
// ✅ Хорошо
var sb strings.Builder
for i := 0; i < 10000; i++ {
sb.WriteString("x")
}
result := sb.String()
// ✅ Идеально
var sb strings.Builder
sb.Grow(10000)
for i := 0; i < 10000; i++ {
sb.WriteString("x")
}
result := sb.String()
// ✅ Альтернатива: strings.Repeat (для одинаковых строк)
result := strings.Repeat("x", 10000)
13. Итоги
✅ Конкатенация += — медленная для циклов
✅ strings.Builder — быстрая альтернатива
✅ Ускорение в 20-100 раз на больших данных
✅ Не требует инициализации — просто var sb strings.Builder
✅ Методы: WriteString, WriteByte, WriteRune, String()
✅ Grow(n) — предварительное выделение памяти
✅ Для миллиона итераций: 136 сек → 0.7 сек
Ключевое правило:
1-2 конкатенации → `+` допустимо
Цикл с конкатенацией → strings.Builder обязательно
Тысячи операций → strings.Builder + Grow()
Практический паттерн:
// ❌ Никогда так в циклах
for i := 0; i < N; i++ {
result += something // медленно
}
// ✅ Всегда так
var sb strings.Builder
for i := 0; i < N; i++ {
sb.WriteString(something) // быстро
}
result := sb.String()
Вывод:
При работе с динамическими строками всегда используйте strings.Builder — это критично для производительности.