В Go преобразование между string и []byte обычно вызывает выделение новой памяти (аллокацию), поскольку строки являются неизменяемыми (immutable), а срезы байт — изменяемые (mutable). Исключение составляют некоторые внутренние оптимизации компилятора (escape analysis), но они не гарантированы и меняются между версиями Go.
Прямое преобразование:
[]byte(s string): всегда аллоцирует новый массив байт и копирует данные.string(b []byte): также всегда копирует данные из массива байт в новую строку.Zero alloction — подход, когда мы избегаем лишних аллокаций. В стандартной библиотеке Go есть небезопасное преобразование через пакет unsafe, позволяющее ссылаться на те же данные без копирования. Использовать это можно только с полным пониманием рисков.
import ( "reflect" "unsafe" ) func BytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } func StringToBytes(s string) []byte { sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) bh := reflect.SliceHeader{ Data: sh.Data, Len: sh.Len, Cap: sh.Len, } return *(*[]byte)(unsafe.Pointer(&bh)) }
Везде, кроме особых случаев, лучше не использовать эти приёмы — это ломает безопасность данных (можно изменить содержимое строки через общий срез, что приведет к ошибкам).
Часто спрашивают: "А правда ли, что при преобразовании string в []byte не будет аллокации, если строка маленькая или константная?"
Правильный ответ: Нет. Независимо от размеров строки компилятор всегда создаст новый срез байт и скопирует содержимое. Исключения — только в unsafe-оптимизациях, не гарантирующих safety и не поддерживаемых официальной документацией Go.
История
Команда писала высоконагруженный сервис по парсингу логов и постоянно преобразовывала входящие строки в срезы байт и обратно для обработки. В пиковых ситуациях garbage collector тратил до 30% процессорного времени на сборку короткоживущих копий. После профилирования выяснилось: каждое преобразование string↔[]byte аллоцировало отдельную область памяти. После внедрения пулов и редизайна API существенную часть конвертаций удалось убрать, снизив нагрузку на GC вдвое.
История
Один из разработчиков оптимизировал работу с JSON, используя unsafe-конвертацию bytes→string для avoid allocations. Поначалу прирост производительности был заметен, но через месяц появились краши: какой-то байтовый буфер переиспользовался, строка указывала на старые изменённые данные. Исправить удалось только через возврат к стандартным копиям и переделке межпроцессного API.
История
При передаче больших бинарных данных по сети решили "оптимизировать" сериализацию, используя BytesToString (без копирования). Однажды строка, которую отправили, оказалась публично видимой, а содержимое среза байт тут же было перезаписано, что привело к отправке мусора и утечке приватной части данных в лог ошибочных пакетов. В итоге дедупликация памяти обернулась утечкой приватных данных!