Ответ
Слайс в Go — это легковесная структура-дескриптор, которая содержит три поля: указатель на начало нижележащего массива, длину (len
) и ёмкость (cap
).
Расширение происходит при использовании встроенной функции append
. Алгоритм следующий:
Проверка ёмкости: Если текущая ёмкость (
cap
) достаточна для добавления новых элементов, они просто помещаются в существующий массив, а длина (len
) слайса увеличивается. Указатель на массив не меняется.Аллокация нового массива: Если ёмкости недостаточно, рантайм Go выделяет новый, больший массив. Затем происходит:
- Копирование всех элементов из старого массива в новый.
- Добавление новых элементов в конец.
- Слайс обновляется: его указатель теперь ссылается на новый массив, а
len
иcap
обновляются.
Стратегия роста ёмкости:
Алгоритм роста не удваивает ёмкость всегда. До Go 1.18, для слайсов до 1024 элементов ёмкость удваивалась, а после — росла на ~25%. Начиная с Go 1.18, алгоритм стал более плавным, но общая идея сохранилась: рост происходит с запасом, чтобы амортизировать стоимость аллокаций.
// Создаем слайс с len=0 и cap=3
s := make([]int, 0, 3)
fmt.Printf("len=%d, cap=%d, ptr=%pn", len(s), cap(s), s)
// Добавляем 3 элемента. Ёмкости хватает.
s = append(s, 1, 2, 3)
fmt.Printf("len=%d, cap=%d, ptr=%pn", len(s), cap(s), s) // ptr тот же
// Добавляем еще один элемент. Ёмкости не хватает.
s = append(s, 4)
// Происходит аллокация нового массива (cap станет 6) и копирование.
fmt.Printf("len=%d, cap=%d, ptr=%pn", len(s), cap(s), s) // ptr изменился
Важный нюанс: Если слайс был создан на основе другого массива или слайса (sliceB := sliceA[:5]
), append
в sliceB
может перезаписать данные в sliceA
, если ёмкости хватает. Если не хватает — будет создан новый массив, и связь разорвется. Это частый источник ошибок.