Слайсы в Go: внутреннее устройство и особенности работы
Основная структура
Слайс в Go — это динамическая структура данных, построенная поверх массива. Внутренне представлен структурой из трёх полей:
type slice struct {
ptr unsafe.Pointer // указатель на первый элемент массива
len int // длина слайса
cap int // ёмкость слайса
}
Ключевое отличие от массива: массив имеет фиксированную длину, являющуюся частью его типа, а слайс — ссылочный тип с динамическим размером.
Создание и инициализация
Основные способы создания слайсов:
// Литерал
s := []int{1, 2, 3} // len=3, cap=3
// Через make
s := make([]int, 5) // len=5, cap=5
s := make([]int, 3, 10) // len=3, cap=10
// Из массива или другого слайса
arr := [5]int{1,2,3,4,5}
s := arr[1:4] // len=3, cap=4
Операции и их стоимость
append — ключевая операция для расширения слайса:
- Если
len < cap: просто записывает элемент и увеличивает len - Если
len == cap: создаёт новый массив с удвоенной ёмкостью (для маленьких слайсов), копирует элементы, добавляет новый
Правило роста ёмкости:
- Для слайсов с cap < 256: удвоение
- Для слайсов с cap >= 256: рост ~25% (формула:
newcap = oldcap + (oldcap + 3*256) / 4)
Важные особенности и подводные камни
1. Слайс — это ссылка на массив
a := []int{1, 2, 3}
b := a
b[0] = 100 // a[0] тоже станет 100
2. append может привести к неожиданному поведению
a := make([]int, 3, 5)
b := a[:3]
b = append(b, 4) // изменяет общий массив
c := a[:4] // c[3] == 4
3. Передача слайса в функцию
func modify(s []int) {
s[0] = 100 // изменит оригинал
s = append(s, 200) // не изменит len оригинала
}
4. Сравнение слайсов
- Слайсы нельзя сравнивать через
==(кроме сравнения с nil) - Для сравнения содержимого используйте
reflect.DeepEqualили цикл
nil vs empty слайс
var s []int // nil слайс, len=0, cap=0
s := []int{} // пустой слайс, len=0, cap=0
s := make([]int, 0) // пустой слайс, len=0, cap=0
Практические рекомендации:
- nil и empty слайс ведут себя одинаково в большинстве случаев
appendработает с nil слайсом- Для сериализации (JSON) nil превращается в
null, empty — в[] - При возврате из функции предпочитайте nil для отсутствия данных
Производительность и оптимизация
Память:
- Слайс — это 24 байта на 64-битной системе
- Сам массив данных хранится отдельно в куче (если превышает размер кеша)
Рекомендации:
- Предварительно выделяйте ёмкость через
makeс cap, если известен размер - Избегайте частых реаллокаций — они вызывают копирование и GC pressure
- Для больших слайсов используйте
copyвместо присваивания среза - При работе с большими данными рассмотрите
sync.Poolдля переиспользования
Типичные ошибки на собеседованиях
- Путаница между длиной и ёмкостью
- Непонимание, что append возвращает новый слайс
- Использование слайса как значения в map (нельзя, нужно использовать массив или структуру)
- Утечка памяти через срез большого массива — маленький слайс держит в памяти весь большой массив
Пример с утечкой памяти и решением
// Проблема: держит весь массив в памяти
func getFirst100(data []int) []int {
return data[:100]
}
// Решение: копировать нужные данные
func getFirst100(data []int) []int {
result := make([]int, 100)
copy(result, data[:100])
return result
}