иконка discount

Скидка 15% по промокоду

кибер понедельник до 01.12иконка discount
CYBER2025
логотип PurpleSchool
логотип PurpleSchool

Функция append в Go Golang

Автор

Олег Марков

Введение

Функция append в Go — один из ключевых инструментов при работе со срезами. Через нее вы будете проходить постоянно, как только начнете писать код на Go немного серьезнее, чем «Hello, world». С ее помощью вы добавляете элементы в срез, динамически меняете его длину, а при необходимости — автоматически увеличиваете емкость под капотом.

Смотрите, я покажу вам, как работает append, чем она отличается от ручного управления массивами, почему иногда срез «вдруг» перестает разделять память с исходным, и как избежать типичных ошибок, связанных с неожиданным ростом памяти, лишними аллокациями и неожиданными побочными эффектами.

Мы разберем:

  • базовый синтаксис append;
  • что происходит со срезом в памяти;
  • как устроены длина и емкость и как они связаны с append;
  • добавление нескольких элементов и объединение срезов;
  • особенности работы со строками, байтовыми срезами и структурами;
  • оптимизацию производительности и типичные ловушки.

Что такое срез и почему append важна

Прежде чем говорить про append, важно понимать, с чем она вообще работает.

Кратко о срезах в Go

Срез в Go — это «обертка» над массивом. Он не хранит данные сам по себе, а содержит три поля:

  • указатель на первый элемент массива (или его часть);
  • длину (len) — сколько элементов логически входит в срез;
  • емкость (cap) — сколько элементов можно хранить, не перевыделяя память.

Схематично это можно представить так:

  • массив: последовательность элементов фиксированной длины;
  • срез: «окно» в этот массив, у которого можно менять длину, но только в пределах емкости.

При создании среза часто используют make:

// Создаем срез из 0 элементов, но с емкостью 5
nums := make([]int, 0, 5)

// Создаем срез длиной 3 и емкостью 3
vals := make([]int, 3)

// nums: len = 0, cap = 5 // vals: len = 3, cap = 3

Функция append как раз и используется, чтобы «наращивать» срез — увеличивать его длину, добавляя новые элементы.

Почему не просто массивы

Массивы в Go имеют фиксированную длину, которая является частью типа. То есть [3]int и [4]int — это разные типы, их нельзя свободно подставлять один вместо другого.

Срезы решают эту проблему: их длина может меняться во время выполнения программы, а append берет на себя всю механику расширения, перевыделения памяти и копирования.

Базовый синтаксис функции append

Начнем с самого простого — как вызвать append и что она возвращает.

Общий вид функции append

Сигнатура функции в документации выглядит примерно так:

func append(slice []T, elems ...T) []T

// slice - исходный срез // elems - один или несколько элементов того же типа T // возвращается новый срез []T

Ключевые моменты:

  • функция принимает срез и один или несколько элементов того же типа;
  • возвращает новый срез;
  • новый срез может ссылаться на тот же массив, что и старый, а может — на новый.

Теперь давайте посмотрим на простой пример.

Пример: добавление одного элемента

package main

import "fmt"

func main() {
    nums := []int{1, 2, 3}
    // Добавляем один элемент в конец среза
    nums = append(nums, 4)

    fmt.Println(nums)      // [1 2 3 4]
    fmt.Println(len(nums)) // 4 - длина увеличилась на 1
}

// Важно - мы присваиваем результат обратно в nums

Обратите внимание: результат вызова append вы почти всегда должны присвоить обратно (либо в ту же, либо в новую переменную). Если этого не сделать, изменения будут потеряны.

Пример: добавление нескольких элементов

Функция поддерживает вариативное количество аргументов, поэтому можно добавить сразу несколько элементов:

package main

import "fmt"

func main() {
    nums := []int{1, 2}

    // Добавляем сразу три элемента
    nums = append(nums, 3, 4, 5)

    fmt.Println(nums)      // [1 2 3 4 5]
    fmt.Println(len(nums)) // 5
}

// append(nums, 3, 4, 5) эквивалентно последовательным вызовам append по одному элементу

Это удобно, когда вы заранее знаете набор элементов, который нужно добавить.

Что происходит при append под капотом

Теперь давайте разберемся, как именно ведет себя срез в памяти при добавлении данных.

Длина и емкость среза

Для любого среза вы можете посмотреть длину и емкость с помощью встроенных функций len и cap:

package main

import "fmt"

func main() {
    s := make([]int, 0, 3)
    fmt.Println(len(s), cap(s)) // 0 3

    s = append(s, 10)
    fmt.Println(len(s), cap(s)) // 1 3

    s = append(s, 20, 30)
    fmt.Println(len(s), cap(s)) // 3 3
}

// Пока len <= cap, новые элементы помещаются в тот же массив

Пока длина не превышает емкость:

  • элементы добавляются в существующий массив;
  • срез просто «расширяет» видимую часть массива.

Когда емкости не хватает

Если вы пытаетесь добавить больше элементов, чем позволяет емкость, Go делает несколько шагов:

  1. Выделяет новый массив большей емкости.
  2. Копирует все элементы старого среза в новый массив.
  3. Добавляет новые элементы.
  4. Возвращает срез, указывающий на новый массив.

Посмотрим на примере, чтобы вам было проще отследить поведение:

package main

import "fmt"

func main() {
    s := make([]int, 0, 2)
    fmt.Printf("step 0: len=%d cap=%d ptr=%p\n", len(s), cap(s), s)

    s = append(s, 1)
    fmt.Printf("step 1: len=%d cap=%d ptr=%p\n", len(s), cap(s), s)

    s = append(s, 2)
    fmt.Printf("step 2: len=%d cap=%d ptr=%p\n", len(s), cap(s), s)

    // Здесь емкость 2, добавляем еще один элемент
    s = append(s, 3)
    fmt.Printf("step 3: len=%d cap=%d ptr=%p\n", len(s), cap(s), s)
}

// ptr=%p - выводит адрес подлежащего массива

Смотрите, при последнем append емкость изменится, а указатель скорее всего станет другим. Это и будет признаком, что Go перевыделил массив.

Изменение стратегии роста емкости

Go не гарантирует строго определенный алгоритм роста емкости, но общее поведение примерно такое:

  • пока емкость небольшая, она может удваиваться;
  • при больших объемах рост может становиться менее агрессивным, чтобы не тратить слишком много памяти.

Итог — чем больше вы заранее знаете о том, сколько элементов вам понадобится, тем лучше бывает задать подходящую емкость через make, чтобы уменьшить количество перераспределений.

Добавление одного и нескольких элементов

Теперь давайте сформализуем основные шаблоны использования append.

Добавление одного элемента в конец

Самый частый случай:

nums := []int{1, 2, 3}

// Добавляем один новый элемент
nums = append(nums, 4)

// Теперь nums содержит [1 2 3 4]

// Такой вызов будет встречаться постоянно в коде на Go

Здесь все прямолинейно: длина увеличилась на 1, емкость осталась прежней или выросла (если она была исчерпана).

Добавление нескольких элементов сразу

Когда вы добавляете несколько элементов, синтаксис практически не отличается:

nums := []int{1, 2}

// Добавляем несколько значений
nums = append(nums, 3, 4, 5)

// nums - [1 2 3 4 5]

// Вариативный параметр позволяет передавать любое количество элементов

Такой подход удобен, когда вы «достраиваете» срез заранее известным набором.

Использование append с литералами срезов

Иногда вы хотите добавить в срез набор значений, но вам удобно оформить их как отдельный срез. В этом случае используется оператор «распаковки» …, о нем чуть ниже, в разделе про слияние срезов.

Для примера сейчас:

extra := []int{4, 5, 6}
nums := []int{1, 2, 3}

// Добавляем элементы другого среза
nums = append(nums, extra...)

// nums - [1 2 3 4 5 6]

// extra... означает - передать каждый элемент extra как отдельный аргумент в append

Слияние и копирование срезов через append

Частая задача — объединить два среза или добавить элементы одного среза в другой.

Слияние двух срезов

Стандартный и idiomatic способ слияния срезов — использование append с «распаковкой»:

package main

import "fmt"

func main() {
    a := []int{1, 2}
    b := []int{3, 4, 5}

    // Добавляем все элементы b в конец a
    a = append(a, b...)

    fmt.Println(a) // [1 2 3 4 5]
}

// Оператор ... разворачивает срез b в последовательность аргументов

Важно:

  • типы элементов срезов должны совпадать;
  • распаковка работает только с срезом, а не с массивом — массив нужно сначала преобразовать в срез.

Преобразование массива в срез для append

Если у вас есть массив, вы можете получить от него срез и потом добавить:

package main

import "fmt"

func main() {
    arr := [3]int{7, 8, 9}
    s := []int{1, 2, 3}

    // Преобразуем массив в срез - arr[:]
    s = append(s, arr[:]...)

    fmt.Println(s) // [1 2 3 7 8 9]
}

// arr[:] - срез, покрывающий весь массив arr

Этот прием удобно использовать, когда вы получаете данные как массив, но хотите динамически расширять их.

Слияние нескольких срезов

Вы можете по очереди «приклеивать» несколько срезов:

package main

import "fmt"

func main() {
    base := []int{1}
    a := []int{2, 3}
    b := []int{4, 5}

    base = append(base, a...)
    base = append(base, b...)

    fmt.Println(base) // [1 2 3 4 5]
}

// Каждый вызов append возвращает новый срез, который мы продолжаем расширять

Если вы знаете итоговый размер заранее, логично создать срез с достаточной емкостью, чтобы избежать лишних аллокаций. Мы еще вернемся к этому в разделе про оптимизацию.

Важные тонкости владения памятью при append

Теперь перейдем к моментам, которые часто вызывают вопросы и баги. Основная тема здесь — разделение подлежащего массива несколькими срезами и изменение поведения после перевыделения.

Общий массив между срезами

Когда вы создаете один срез из другого (через срезовую операцию), оба среза могут указывать на один и тот же массив:

package main

import "fmt"

func main() {
    base := []int{1, 2, 3, 4}
    // Создаем срез, который "смотрит" на часть base
    part := base[:2] // [1 2]

    // Меняем элемент через part
    part[0] = 100

    fmt.Println(base) // [100 2 3 4]
    fmt.Println(part) // [100 2]
}

// part и base разделяют общий массив, изменения видны в обоих

Пока вы не превысили емкость одного из срезов, они продолжают разделять один массив.

Как append может разорвать общую память

Теперь давайте посмотрим, что произойдет при вызове append на одном из срезов:

package main

import "fmt"

func main() {
    base := []int{1, 2, 3}
    part := base[:2] // len=2, cap, скорее всего, 3

    // Добавляем элемент в part
    part = append(part, 99)

    fmt.Println("base:", base) // Возможный результат - [1 2 99]
    fmt.Println("part:", part) // [1 2 99]
}

// Здесь емкость part позволяет добавить еще один элемент без аллокации

В этом примере:

  • емкость part равна емкости base (или не меньше);
  • при append не требуется перевыделять массив;
  • поэтому изменения затрагивают общий массив, и base тоже меняется.

Теперь давайте расширим part еще на один элемент:

package main

import "fmt"

func main() {
    base := []int{1, 2, 3}
    part := base[:2] // len=2, cap=3 (обычно)

    part = append(part, 99) // len=3, cap=3
    fmt.Println("after 1st append:")
    fmt.Println("base:", base) // [1 2 99]
    fmt.Println("part:", part) // [1 2 99]

    // Второй append с большой вероятностью приведет к новой аллокации
    part = append(part, 100)

    fmt.Println("after 2nd append:")
    fmt.Println("base:", base) // [1 2 99] - не меняется
    fmt.Println("part:", part) // [1 2 99 100] - уже на новом массиве
}

// Второй append выходит за пределы емкости и создает новый массив

Как видите, после второго append:

  • base продолжает указывать на старый массив;
  • part теперь указывает на новый массив;
  • дальнейшие изменения через part не затронут base.

Это поведение часто становится причиной неожиданных эффектов, если вы рассчитываете, что несколько срезов по-прежнему разделяют память, а после расширения один из них «отрывается» от общего массива.

Безопасное копирование через append

Иногда вы хотите специально создать независимую копию среза. Для этого можно воспользоваться следующей идиомой:

package main

import "fmt"

func main() {
    src := []int{1, 2, 3}

    // Создаем новый срез нулевой длины, но с той же емкостью
    dst := append([]int(nil), src...)

    // Меняем src
    src[0] = 100

    fmt.Println("src:", src) // [100 2 3]
    fmt.Println("dst:", dst) // [1 2 3] - независимая копия
}

// append к пустому nil-срезу создает новый массив и копирует элементы src

Вы можете также явно задать нужную емкость:

dst := make([]int, 0, len(src))
dst = append(dst, src...)

// Здесь вы контролируете емкость dst явно

Особенности append при работе со строками и байтовыми срезами

Часто append используют не только для чисел и структур, но и для работы с байтовыми данными, особенно при сборке строк или бинарных буферов.

Нельзя напрямую append к строке

Строка в Go неизменяема. Вы не можете взять строку и «добавить» к ней символы через append. Вам понадобится срез байтов или рун.

Пример с байтовым срезом:

package main

import "fmt"

func main() {
    // Исходная строка
    s := "go"

    // Преобразуем строку в срез байтов
    b := []byte(s)

    // Добавляем символы в виде байтов
    b = append(b, 'l', 'a', 'n', 'g')

    // Преобразуем обратно в строку
    result := string(b)

    fmt.Println(result) // "golang"
}

// Строка s не менялась, мы работали только с срезом b

Если вам нужно работать с Unicode-символами, которые занимают больше одного байта, можно использовать срез рун:

runes := []rune("го")
runes = append(runes, 'л', 'а', 'н', 'г')
result := string(runes)

// []rune корректно учитывает многобайтовые символы

Добавление одного байта к []byte

Добавить один байт в конец очень просто:

buf := []byte{1, 2, 3}

// Добавляем один байт со значением 4
buf = append(buf, 4)

// buf - [1 2 3 4]

// Это часто применяется при формировании протокольных сообщений или бинарных данных

Объединение []byte через append

Когда вы складываете два байтовых среза, снова используете оператор …:

header := []byte{0x01, 0x02}
body := []byte{0x0A, 0x0B, 0x0C}

// Создаем буфер и добавляем сначала header, потом body
buf := make([]byte, 0, len(header)+len(body))
buf = append(buf, header...)
buf = append(buf, body...)

// Такой подход уменьшает количество аллокаций и подходит для сборки пакета

Работа с append и структурами

Функция append одинаково хорошо работает с любыми типами, включая структуры и пользовательские типы.

Добавление структур в срез

package main

import "fmt"

type User struct {
    ID   int
    Name string
}

func main() {
    users := []User{
        {ID: 1, Name: "Alice"},
        {ID: 2, Name: "Bob"},
    }

    // Добавляем нового пользователя
    users = append(users, User{ID: 3, Name: "Carol"})

    fmt.Println(users)
}

// append копирует значение структуры в срез, а не хранит "указатель" на локальную переменную

Если вы хотите работать с указателями на структуры, то тип среза будет []*User, но логика append сохраняется.

Добавление элементов в поле-срез внутри структуры

Иногда структура сама содержит поле типа срез. Тогда вы используете append к этому полю:

package main

import "fmt"

type Group struct {
    Name  string
    Users []string
}

func main() {
    g := Group{Name: "admins"}

    // Добавляем имена пользователей в поле Users
    g.Users = append(g.Users, "alice")
    g.Users = append(g.Users, "bob", "carol")

    fmt.Println(g.Users) // [alice bob carol]
}

// Важно - не забывать присваивать результат обратно в g.Users

Удаление и вставка элементов через append

Хотя в языке нет отдельной функции для удаления или вставки в середину среза, append можно использовать как строительный блок для таких операций.

Удаление элемента по индексу

Давайте разберемся, как «удалить» элемент из середины среза. По сути, мы формируем новый срез, обходя ненужный элемент:

package main

import "fmt"

func main() {
    s := []int{10, 20, 30, 40, 50}

    // Удаляем элемент с индексом 2 (значение 30)
    idx := 2
    s = append(s[:idx], s[idx+1:]...)

    fmt.Println(s) // [10 20 40 50]
}

// s[:idx] - элементы до удаляемого // s[idx+1:]... - элементы после удаляемого // append склеивает их в новый срез

Важно помнить:

  • при таком подходе элементы после удаленного смещаются влево;
  • старый элемент все еще может оставаться в памяти подлежащего массива, пока срез на него указывает косвенно, но доступ к нему по индексу вы уже не получите.

Удаление диапазона элементов

Если нужно убрать несколько элементов подряд:

package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5, 6, 7}

    // Удалим элементы с индексов [2, 5) - то есть 3, 4, 5
    start := 2
    end := 5

    s = append(s[:start], s[end:]...)

    fmt.Println(s) // [1 2 6 7]
}

// Мы вырезали "середину" среза и склеили оставшиеся части

Вставка элемента в середину

Вставка — это обратная операция: нам нужно освободить место внутри среза и поместить новый элемент.

package main

import "fmt"

func main() {
    s := []int{1, 2, 5, 6}

    // Вставим число 3 на позицию с индексом 2
    idx := 2
    val := 3

    // Расширяем срез, добавляя "пустое" место в конце
    s = append(s, 0)

    // Сдвигаем хвост вправо на один элемент
    copy(s[idx+1:], s[idx:])

    // Записываем новое значение
    s[idx] = val

    fmt.Println(s) // [1 2 3 5 6]
}

// Здесь мы использовали комбинацию append и copy для аккуратного сдвига элементов

Здесь append используется для увеличения длины на 1 (создание «дыры» в конце), а copy — для сдвига хвоста вправо.

Append, nil-срезы и пустые срезы

Еще одна важная тема — как append ведет себя с nil и пустыми срезами и есть ли между ними разница.

Append к nil-срезу

nil-срез — это срез, у которого нет подлежащего массива и указатель равен nil:

var s []int // это nil-срез

fmt.Println(s == nil) // true

// len(s) 0 и cap(s) 0

Если вы вызовете append на nil-срезе, Go просто создаст новый массив и вернет корректный срез:

package main

import "fmt"

func main() {
    var s []int // nil

    s = append(s, 1, 2, 3)

    fmt.Println(s)        // [1 2 3]
    fmt.Println(len(s))   // 3
    fmt.Println(cap(s) >= 3) // true - емкость становится больше нуля
}

// Это удобный способ инициализации среза по мере накопления данных

Такой подход часто используют, когда вы не знаете заранее размер, но хотите постепенно накапливать элементы.

Пустой, но не nil-срез

Пустой срез может быть как nil, так и не-nil. Например:

s1 := []int{}        // пустой, но не nil
var s2 []int         // nil-срез

fmt.Println(s1 == nil) // false
fmt.Println(s2 == nil) // true

// Оба среза имеют len=0, но внутреннее состояние разное

Для append разницы нет: в обоих случаях он будет работать одинаково — добавит элементы, создав или переиспользовав массив.

Разница может быть важна при сериализации или сравнении с nil, но к работе append это напрямую не относится.

Оптимизация и производительность append

Теперь давайте обсудим, как использовать append эффективно, чтобы лишний раз не перегружать сборщик мусора и не тратить память.

Предварительное выделение емкости

Если вы примерно знаете, сколько элементов вам понадобится, разумно заранее задать емкость:

// Мы знаем, что нам нужно примерно 1000 элементов
items := make([]int, 0, 1000)

// Заполняем срез в цикле
for i := 0; i < 1000; i++ {
    items = append(items, i)
}

// Благодаря заранее установленной емкости будет минимум аллокаций

Без предварительной емкости Go будет периодически перевыделять массив и копировать данные при переполнении емкости, что может заметно замедлить работу на больших объемах.

Использование len при точном размере

Если вы знаете точный размер заранее, можно создать срез нужной длины и напрямую записывать элементы по индексу, минимизируя необходимость в append:

n := 5
s := make([]int, n)

for i := 0; i < n; i++ {
    s[i] = i * 2
}

// Здесь мы вообще не используем append, так как размер известен заранее

Это не альтернатива append, а скорее дополнительный инструмент: вы можете комбинировать подходы, где это удобно.

Избегание лишних копирований

При частом слиянии срезов учитывайте:

  • каждый append, который выходит за пределы емкости, копирует все элементы;
  • объединение большого количества маленьких срезов по одному может быть дороже, чем один крупный append.

Иногда полезно заранее посчитать суммарную длину и создать один буфер нужной емкости:

a := []int{1, 2}
b := []int{3, 4, 5}
c := []int{6, 7}

totalLen := len(a) + len(b) + len(c)
result := make([]int, 0, totalLen)

result = append(result, a...)
result = append(result, b...)
result = append(result, c...)

// Здесь у нас будет ровно одна аллокация под result

Частые ошибки и ловушки при использовании append

Давайте соберем в одном месте типичные проблемы, с которыми сталкиваются разработчики при работе с append.

Ошибка: забыли присвоить результат append

Очень распространенный случай:

s := []int{1, 2, 3}

// Ошибка - результат append никуда не сохранен
append(s, 4)

// s по-прежнему [1 2 3]

// append всегда возвращает новый срез, который нужно сохранить

Правильно:

s = append(s, 4)

Ошибка: изменение общего массива «случайно»

Проблема возникает, когда вы создаете несколько срезов, а потом вызываете append на одном из них и неожиданно меняете другой. Смотрите пример:

package main

import "fmt"

func main() {
    base := []int{1, 2, 3, 4}
    a := base[:2] // [1 2]
    b := base[1:] // [2 3 4]

    // Меняем a через append
    a = append(a, 99) // len(a) <= cap(a), поэтому используется общий массив

    fmt.Println("base:", base) // [1 2 99 4]
    fmt.Println("a:", a)       // [1 2 99]
    fmt.Println("b:", b)       // [2 99 4] - изменился "неожиданно"
}

// b изменился, потому что все три среза делили один массив

Если вы хотите избежать таких эффектов, либо:

  • создавайте независимую копию среза (через append к nil или make+copy);
  • либо четко контролируйте емкость, чтобы принудительно спровоцировать аллокацию и отделение.

Ошибка: ожидание, что append «очищает память»

Некоторые разработчики думают, что если сделать:

s = s[:0]

и потом вызывать append, то старые данные будут удалены. На самом деле:

  • срез перестает ссылаться на элементы на уровне длины, но емкость и подлежащий массив остаются;
  • сборщик мусора не освободит память, если где-то еще есть ссылки на тот же массив.

Если нужно реально освободить память, нужно:

  • либо дать сборщику мусора возможность освободить данные (убрав все ссылки);
  • либо создать новый срез с небольшой емкостью и при необходимости скопировать нужные элементы.

Ошибка: неправильная работа с индексами при удалении

При удалении элементов через append легко ошибиться с диапазонами, например:

s := []int{1, 2, 3, 4}
// Неправильно - может привести к панике при выходе за границы
// s = append(s[:2], s[4:]...)

// Если длина меньше, чем вы ожидаете, выражение s[4:] вызовет панику

Здесь важно:

  • тщательно проверять индексы;
  • помнить, что диапазон в срезе [i:j] включает i и исключает j.

Заключение

Функция append в Go — это основной способ динамически изменять срезы. Она берет на себя всю сложную часть: от проверки емкости до перевыделения памяти и копирования элементов. При этом от вас требуется понимать, к чему именно вы применяете append, как устроены длина и емкость среза, и какие эффекты влечет за собой возможное разделение подлежащего массива несколькими срезами.

Кратко подведем основные моменты:

  • append всегда возвращает новый срез, и его нужно присваивать;
  • пока длина не превышает емкость, данные остаются в том же массиве;
  • при превышении емкости создается новый массив, и срез «отрывается» от старого;
  • с помощью append и оператора … удобно объединять срезы и копировать данные;
  • через комбинацию append и срезовых операций можно реализовать удаление и вставку;
  • nil-срезы прекрасно работают с append, их можно безопасно расширять;
  • для производительности полезно заранее выделять емкость с помощью make;
  • будьте аккуратны с разделяемыми срезами, чтобы не получить неожиданные побочные эффекты.

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

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как принудительно сделать так, чтобы append точно создал новый массив и не использовал старый

Создайте новый срез с нулевой длиной и достаточной емкостью, а затем используйте append:

dst := make([]int, 0, len(src))
dst = append(dst, src...)

// dst точно будет использовать новый массив

Либо используйте append к nil-срезу:

dst := append([]int(nil), src...)

Чем отличается копирование через append от функции copy

copy копирует элементы из одного среза в другой уже существующий срез и возвращает количество скопированных элементов. append создает или расширяет подлежащий массив и увеличивает длину среза.

Если вам нужно только перенести данные в уже созданный буфер фиксированного размера, используйте copy. Если вы хотите динамически расширять срез, используйте append.

Можно ли использовать append в конкурентном коде без синхронизации

Сам по себе append не потокобезопасен, если несколько горутин используют один и тот же срез для записи. Вам нужно синхронизировать доступ:

  • либо через mutex;
  • либо через каналы и выделение отдельной горутины, которая управляет срезом.

Если каждая горутина работает только со своей копией среза, то дополнительных мер не требуется.

Как уменьшить емкость среза, чтобы сборщик мусора мог освободить лишнюю память

Можно создать новый срез меньшей емкости и скопировать туда данные:

s = s[:n]                     // оставляем нужную длину
tmp := make([]int, len(s))    // создаем срез без "запаса" емкости
copy(tmp, s)
s = tmp

После этого старый массив, на который ссылался исходный срез, может быть собран сборщиком мусора, если нет других ссылок.

Почему длина и емкость могут сильно различаться после множества операций append и удаления

Каждый append, который выходит за пределы текущей емкости, создает новый массив с увеличенным запасом. Удаления через срезы и append не уменьшают емкость, они только меняют длину. В итоге вы можете получить срез с небольшой длиной, но очень большой емкостью. В таких случаях имеет смысл выполнить «сжатие» — создать новый срез через make с нужной длиной и скопировать в него данные.

Строка таблицы HTML tr - полное руководство для верстальщиков и разработчиковСтрелочка вправо

Постройте личный план изучения Html до уровня Middle — бесплатно!

Html — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Html

Тег section в HTML - семантическая разметка структуры страницыТег nav в HTML - полное руководство по семантической навигацииТег main в HTML - подробное руководство по использованиюТег header в HTML - полное практическое руководствоТег footer в HTML - назначение семантика и практические примерыТег figure в HTML - как правильно оформлять иллюстрации и подписиТег figcaption в HTML - подробное руководство с примерамиТег aside в HTML - назначение правильная семантика и примеры
Текстовая область HTML textarea - практическое руководствоВыпадающий список HTML select - полное руководство для разработчиковОпция списка HTML option - как работает и как правильно использоватьАтрибут method в HTML - как правильно отправлять данные формыЗаголовок группы HTML legend - как правильно использовать и оформлятьТег input в HTML - типы атрибуты валидация и примерыТег формы form в HTMLГруппа полей HTML fieldsetАтрибут action в HTML - как правильно задавать адрес отправки формы
Открыть базу знаний

Лучшие курсы по теме

изображение курса

HTML и CSS

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий