Опишите механизм расширения слайса в Go при использовании `append`.

Ответ

Слайс в Go — это структура-обертка над нижележащим массивом, которая включает в себя три поля: указатель на массив, длину (len) и ёмкость (cap).

Когда вы добавляете элементы в слайс с помощью функции append, происходит следующее:


  1. Проверка ёмкости: Go проверяет, достаточно ли текущей ёмкости (cap) для добавления новых элементов. Если len нового слайса меньше или равна cap старого, элементы просто добавляются в существующий массив, и len слайса увеличивается. Указатель на массив не меняется.



  2. Аллокация нового массива: Если ёмкости недостаточно, рантайм Go выделяет память под новый, больший массив. Происходит расширение.


Стратегия роста ёмкости:

  • Если текущая ёмкость меньше 256 элементов, новая ёмкость будет в два раза больше старой.
  • Если ёмкость больше или равна 256, она увеличивается по более плавному алгоритму, постепенно приближаясь к множителю 1.25.

  1. Копирование данных: Все элементы из старого массива копируются в новый.



  2. Обновление слайса: Слайс обновляется: его указатель теперь ссылается на новый массив, а 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), чтобы избежать многократных переаллокаций.
  • После расширения слайс указывает на совершенно новый массив. Старый массив, если на него больше нет ссылок, будет утилизирован сборщиком мусора.