В чём разница между длиной и ёмкостью массива?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между длиной и ёмкостью в Go
В языке Go понятия длины (length) и ёмкости (capacity) являются фундаментальными, но часто путаемыми, особенно при работе со срезами (slices). Важно понимать, что для массивов (arrays) эти понятия ведут себя иначе, чем для срезов.
Для массивов
Массив в Go — это тип данных с фиксированным размером. Длина массива определяется при его объявлении и не может быть изменена в runtime.
// Массив из 5 целых чисел
var arr [5]int // Длина = 5, ёмкость = 5
Для массива:
- Длина (length) — это количество элементов, которые содержатся в массиве. Для массива она постоянна.
- Ёмкость (capacity) — для массива равна длине. Понятие ёмкости для массива практически не используется, так как массив не может "расти" или иметь "запас" памяти.
Функции len() и cap() для массива вернут одинаковое значение:
arr := [3]string{"a", "b", "c"}
fmt.Println(len(arr)) // 3
fmt.Println(cap(arr)) // 3 (ёмкость равна длине)
Для срезов (где разница критически важна)
Срез (slice) — это динамическая обёртка над массивом. Именно при работе со срезами различие между длиной и ёмкостью становится ключевым.
- Длина (length) — текущее количество элементов в срезе, к которым можно обратиться по индексу (
slice[0],slice[1]и т.д.). Это то, что видит пользователь среза. - Ёмкость (capacity) — общее количество элементов в базовом массиве (underlying array), начиная с первого элемента среза. Это "запас памяти", выделенный для будущего роста среза без переаллокации (копирования в новый массив большего размера).
// Создаём срез с начальной длиной 3 и ёмкостью 5
mySlice := make([]int, influence 3, 5) // Длина = 3, Ёмкость = 5
mySlice[0] = 1 // OK
mySlice[1] = 2 // OK
mySlice[2] = 3 // OK
// mySlice[3] = 4 // PANIC: индекс за пределами длины (3)
// mySlice[4] = 5 // PANIC: индекс за пределами длины (3)
fmt.Println(len(mySlice)) // 3
fmt.Println(cap(mySlice)) // 5
Практическое значение ёмкости среза
Ёмкость — это буфер для эффективного добавления элементов с помощью функции append.
-
Пока количество элементов меньше ёмкости,
appendдобавляет элемент в уже выделенную память базового массива, увеличивая только длину. Это очень быстрая операция.mySlice = append(mySlice, 4) // Длина станет 4, Ёмкость останется 5 mySlice = append(mySlice, 5) // Длина станет 5, Ёмкость останется 5 -
При попытке добавить элемент, когда длина равна ёмкости, происходит переаллокация: Go создаёт новый базовый массив (обычно удваивая ёмкость, но алгоритм может меняться), копирует в него все старые элементы, добавляет новый и возвращает срез, ссылающийся на этот новый массив. Эта операция дороже по производительности.
mySlice = append(mySlice, 6) // Длина = 6, Ёмкость может стать 10 (удвоение)
Почему это важно?
- Производительность: Предварительное выделение достаточной ёмкости (
make([]T, 0, expectedSize)) для известного или ожидаемого количества элементов позволяет избежать многократных дорогостоящих переаллокаций при наполнении среза. - Оптимизация памяти: Слишком большой начальный capacity для маленьких срезов ведёт к неэффективному использованию памяти.
- Поведение операций: Операции "ре-слайсинга" (
slice[low:high]) создают новый срез, который делит базовый массив с оригинальным срезом. Его ёмкость будет считаться от первого элемента нового среза до конца базового массива.original := []int{1, 2, 3, 4, 5} // len=5, cap=5 subSlice := original[1:3] // [2, 3], len=2, cap=4! // subSlice может быть увеличен до 4 элементов без переаллокации, // но это повлияет на original[4].
Итог
- Для массива:
длина == ёмкость. Оба значения фиксированы и неизменны. - Для среза:
длина <= ёмкость.
* **Длина** — это "видимая" часть, рабочая область среза.
* **Ёмкость** — это общий запас памяти "под капотом", который определяет, когда произойдёт дорогостоящее перевыделение памяти при росте среза.
Понимание этой разницы — обязательный навык для написания эффективного и корректного кода на Go, особенно при работе с большими или часто изменяемыми коллекциями данных.