← Назад к вопросам

Что произойдет при использовании append в slice, если не осталось места?

1.3 Junior🔥 171 комментариев
#Основы Go

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм работы append при переполнении capacity

При использовании встроенной функции append() для добавления элементов в слайс, когда в базовом массиве не остаётся свободной ёмкости (capacity), происходит следующее:

1. Создание нового массива

Go создаёт новый базовый массив большего размера. Стандартный алгоритм увеличения capacity работает так:

  • Для слайсов с capacity < 1024 — новый capacity удваивается
  • Для слайсов с capacity ≥ 1024 — новый capacity увеличивается на 25%
  • Точный размер может немного варьироваться в зависимости от реализации runtime
package main

import "fmt"

func main() {
    slice := make([]int, 0, 3) // capacity = 3
    fmt.Printf("До append: len=%d, cap=%d, ptr=%p\n", 
        len(slice), cap(slice), &slice[0])
    
    slice = append(slice, 1, 2, 3, 4) // Добавляем 4-й элемент
    fmt.Printf("После append: len=%d, cap=%d, ptr=%p\n", 
        len(slice), cap(slice), &slice[0])
}

2. Копирование данных

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

// Внутренняя логика (упрощённо)
func appendInt(x []int, y int) []int {
    var z []int
    zlen := len(x) + 1
    
    if zlen <= cap(x) {
        // Есть место — расширяем слайс
        z = x[:zlen]
    } else {
        // Нет места — создаём новый массив
        zcap := zlen
        if zcap < 2*len(x) {
            zcap = 2 * len(x)
        }
        z = make([]int, zlen, zcap)
        copy(z, x) // Ключевое копирование!
    }
    z[len(x)] = y
    return z
}

3. Изменение дескриптора слайса

Слайс в Go — это дескриптор, содержащий три поля:

  • Указатель на базовый массив
  • Длину (len)
  • Ёмкость (cap)

После переаллокации:

  • Указатель меняется на новый массив
  • Длина увеличивается на количество добавленных элементов
  • Ёмкость устанавливается в размер нового массива

4. Важные следствия

🔄 Изменение ссылок

a := []int{1, 2, 3}
b := a // Оба слайса указывают на один массив

a = append(a, 4) // Переаллокация!
a[0] = 99

fmt.Println(a) // [99 2 3 4]
fmt.Println(b) // [1 2 3] — b не изменился!

📊 Производительность

Переаллокация — дорогая операция O(n), поэтому:

  • При известном конечном размере лучше использовать make([]T, 0, knownCapacity)
  • Частые переаллокации могут создать нагрузку на GC

🔍 Определение переаллокации

func main() {
    s := []int{1, 2, 3}
    fmt.Printf("Исходный: %p\n", &s[0])
    
    // Первый append без переаллокации
    s = append(s, 4)
    fmt.Printf("После 4: %p\n", &s[0])
    
    // Второй append с переаллокацией (capacity было 3, станет 6)
    s = append(s, 5, 6, 7)
    fmt.Printf("После 7: %p\n", &s[0]) // Адрес изменился!
}

Практические рекомендации

  1. Предварительное выделение:

    // Плохо: возможны множественные переаллокации
    var users []User
    for i := 0; i < 1000; i++ {
        users = append(users, User{})
    }
    
    // Хорошо: одна аллокация
    users := make([]User, 0, 1000)
    for i := 0; i < 1000; i++ {
        users = append(users, User{})
    }
    
  2. Мониторинг capacity:

    func monitorGrowth(s []int) {
        for i := 0; i < 10; i++ {
            oldCap := cap(s)
            s = append(s, i)
            newCap := cap(s)
            if oldCap != newCap {
                fmt.Printf("Переаллокация: %d -> %d\n", oldCap, newCap)
            }
        }
    }
    
  3. Особенность среза слайса:

    base := make([]int, 0, 5)
    slice := base[:0] // len=0, cap=5, та же память
    // append в slice будет использовать capacity base
    

Таким образом, append() обеспечивает автоматическое управление памятью, но требует понимания механизма переаллокации для написания эффективного кода. Всегда анализируйте паттерны использования слайсов в критичных по производительности участках кода.