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