Материал проходит вычитку — возможны неточности.
Урок 9 из 118

Изменить строку: []byte и Builder

После инцидента Сергей попросил Мишу почистить сборку путей к файлам. Склейка строк на горячем пути тормозила заметно, и Миша переписал её без промежуточных копий. Аня проверила правку на том же загрузочном тесте — стало быстрее.

Главная мысль

чтобы «изменить» неизменяемую строку, её копируют через []byte или собирают в strings.Builder — оба пути дают новую строку, но Builder делает это одним финальным преобразованием без копии.

Рано или поздно строку нужно изменить, а неизменяемость этому мешает. Стандартный обходной путь — сходить через слайс байтов. Преобразование string → []byte и обратно копирует данные, поэтому изменение слайса не задевает исходную строку.

Правка через []byte

Go

Копирование здесь обязательно: строка неизменяема, слайс — изменяем, и если бы они делили один массив, слайс мог бы нарушить неизменяемость строки. Спецификация требует независимой семантики значений — изменяемый слайс не может делить данные с неизменяемой строкой. Поэтому преобразование даёт независимое значение: обычно это физическая копия.

Отсюда типичный паттерн, когда строку нужно «исправить»: преобразовать в []byte, изменить байты, преобразовать обратно.

func asciiUpper(s string) string {
    b := []byte(s)
    for i := range b {
        if b[i] >= 'a' && b[i] <= 'z' {
            b[i] -= 'a' - 'A'
        }
    }
    return string(b)
}

Слово ascii в названии — честное предупреждение о границе метода: функция делает заглавными латинские буквы, а кириллицу оставляет как есть, потому что работает побайтово и не знает про многобайтовые символы.

Сборка по кускам через Builder

Преобразования копируют данные, а значит, в горячем цикле каждое string(b) или []byte(s) — это лишняя аллокация. Если строка собирается по частям, strings.Builder накапливает байты в своём буфере и делает одно преобразование в конце, а не по одному на каждый кусок:

var b strings.Builder
for _, part := range parts {
    b.WriteString(part)
}
result := b.String() // одно преобразование вместо len(parts)

Есть и частный случай, когда компилятор выбрасывает копию сам: если результат []byte(s) нужен только для чтения и нигде не сохраняется, копия может не создаваться. Это оптимизация компилятора, опираться на неё при проектировании не стоит.

В следующем уроке

Вы узнаете:

  • как strings.Join реализует Builder-паттерн
  • как проверить оптимизацию m[string(b)] бенчмарком
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

Почему преобразование string(b) обязано копировать данные?

Вопрос 2 из 3

В каком случае компилятор может не копировать при []byte(s) → string(b)?

Вопрос 3 из 3

Можно ли опираться на оптимизацию m[string(b)] при проектировании?

Итоги

Строку не меняют — создают новую: либо через копию в []byte, либо через strings.Builder, который накапливает куски и преобразует один раз в конце — финальный String() без копии.

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки