Что возвращает функция cap применительно к слайсу
Перейти к содержимому

Что возвращает функция cap применительно к слайсу

  • автор:

Принцип работы типа slice в GO

Как работать со срезами, объяснено в в блоге GO. Далее рассмотрим внутренее устройство среза.

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

От массива срез отличает следующее:

  • Срез — всегда ссылка на массив;
  • Срез может менять свой размер и динамически аллоцировать память;
type slice struct  array unsafe.Pointer len int cap int >

len (length, длина) — текущая длина среза, cap (capacity, вместимость) — длина внутреннего массива. Оба эти параметра можно задать при вызове функции make :

s := make( []int, 10, // len 10, // cap )

Cap — ключевой параметр для аллокации памяти, влияет на производительность вставки в срез. Рассмотрим поведение среза при увеличении его размера.

a := []int1> b := a[0:1] b[0] = 0 fmt.Println(a) // [0] a[0] = 1 fmt.Println(b) // [1]

Мы получили срез b из среза a. Далее видим, что изменение в одном срезе влияют на другой. Оба среза ссылаются на один и тот же массив. Теперь дополним пример вставкой элементов в срез a:

a := []int1> b := a[0:1] a = append(a, 2) a[0] = 10 fmt.Println(a, b) // [10 2] [0]

Теперь значения у элементов срезов разные. Теперь срезы ссылаются на разный массив. Понять, почему так произошло, помогут исходники функции growslice. В результате изменения cap среза всегда произойдет копирование данных массива:

memmove(p, old.array, lenmem)

Теперь рассмотрим, каким образом происходит фактическое увеличение cap:

newcap := old.cap doublecap := newcap + newcap if cap > doublecap  newcap = cap > else  if old.len  1024  newcap = doublecap > else  // Check 0 < newcap to detect overflow// and prevent an infinite loop. for 0  newcap && newcap  cap  newcap += newcap / 4 > // Set newcap to the requested cap when // the newcap calculation overflowed. if newcap = 0  newcap = cap > > >
  • При увеличении cap, массив будет скопирован;
  • Размер аллоцированной памяти будет расти по своей внутренней логике, лишь отчасти связанной с требуемой cap;

Делаем вывод — чтобы избежать новых аллокаций и копирования, нужно еще при создании среза выставлять большой cap.

В примере ниже мы изменили лишь cap у среза a. Но теперь после вставки в него, копирование не произошло и оба среза даже после вставки ссылаются на один массив:

a := make([]int, 1, 2) a[0] = 1 b := a[0:1] b[0] = 0 fmt.Println(a, b) // [0] [0] a = append(a, 2) a[0] = 10 fmt.Println(a, b) // [10 2] [10] 

В блоге GO предлагают взять контроль над увеличением среза в свои руки — использовать свою функцию вместо append.
Но мы сможем лишь еще больше увеличить рост аллоцируемой памяти.

func AppendByte(slice []byte, data ...byte) []byte  m := len(slice) n := m + len(data) if n > cap(slice)  // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice > slice = slice[0:n] copy(slice[m:n], data) return slice >

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

Другие статьи

  • Работа с данными в конкурентных программах на GO
  • GO templates
  • Принцип работы типа map в GO
  • Golang regexp: как заматчить перенос строки
  • Профилирование

Golang

Срезы оборачивают массивы, чтобы дать более общий, мощный и удобный интерфейс с последовательностями данных. За исключением предметов с явным измерением, такие как матрицы преобразования, большая часть программирования массивов в Go делается со срезами, а не с простыми массивами.

Срезы содержат ссылки на базовый массив, и если вы назначите один срез к другому, оба будут ссылаться на один и тот же массив. Если функция принимает аргумент срез, изменения, которые она вносит в элементы среза, будут видны вызывающей стороне, аналогично передаче указателя на базовый массив. Read функция поэтому может принимать аргумент срез, а не указатель и счет; длина внутри среза устанавливает верхний предел того, сколько данных для чтения. Вот сигнатура метода Read типа File в пакете os :

func (f *File) Read(buf []byte) (n int, err error)

Метод возвращает количество прочитанных байтов и значение ошибки, если она есть. Для считывания первых 32 байт большего буфера buf , сделаем срез буфера.

n, err := f.Read(buf[0:32])

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

var n int var err error for i := 0; i < 32; i++ < nbytes, e := f.Read(buf[i:i+1]) // Читаем один байт. if nbytes == 0 || e != nil < err = e break >n += nbytes >

Длина среза может быть изменена до тех пор, пока он все еще вписывается в пределы базового массива; просто назначьте его на срез самого себя. Емкость среза, доступная встроенной функции cap , сообщает максимальную длину среза, которую можно предполагать. В следующем примере представлена функция для добавления данных в срез. Если данные превышает емкость, срез перераспределяется. Полученный срез возвращается. Функция использует тот факт, что len и cap являются законными применительно к nil и возвращает 0 в этом случае.

func Append(slice, data []byte) []byte < l := len(slice) if l + len(data) >cap(slice) < // реаллоцируем // Аллоцирем в двойном размере требуемого, // для будущего роста. newSlice := make([]byte, (l+len(data))*2) // copy функция предопределена // и работает для любого типа среза. copy(newSlice, slice) slice = newSlice >slice = slice[0:l+len(data)] copy(slice[l:], data) return slice >

Мы должны вернуть срез позже, потому что, хотя Append может изменять элементы slice , сам срез (структура данных времени выполнения, содержащая указатель, длину и емкость) передается по значению.

Идея добавления к срезу очень полезна, и используется встроенной функцией append .

  • Эффективный Go: данные, аллокация с помощью make
  • Основы Go: массивы (arrays)
  • Основы Go: срезы (slices)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *