Как очистить кусочек в Go?


125

Как лучше всего очистить фрагмент в Go?

Вот что я нашел на форумах го :

// test.go
package main

import (
    "fmt"
)

func main() {
    letters := []string{"a", "b", "c", "d"}
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    // clear the slice
    letters = letters[:0]
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
}

Это верно?

Чтобы уточнить, буфер очищается, поэтому его можно использовать повторно.

Примером может служить функция Buffer.Truncate в пакете bytes.

Обратите внимание, что Reset просто вызывает Truncate (0). Похоже, что в этом случае строка 70 будет оценивать: b.buf = b.buf [0: 0]

http://golang.org/src/pkg/bytes/buffer.go

// Truncate discards all but the first n unread bytes from the buffer.
60  // It panics if n is negative or greater than the length of the buffer.
61  func (b *Buffer) Truncate(n int) {
62      b.lastRead = opInvalid
63      switch {
64      case n < 0 || n > b.Len():
65          panic("bytes.Buffer: truncation out of range")
66      case n == 0:
67          // Reuse buffer space.
68          b.off = 0
69      }
70      b.buf = b.buf[0 : b.off+n]
71  }
72  
73  // Reset resets the buffer so it has no content.
74  // b.Reset() is the same as b.Truncate(0).
75  func (b *Buffer) Reset() { b.Truncate(0) }

1
Быстрый тест на: play.golang.org/p/6Z-qDQtpbg, кажется, предполагает, что он будет работать (не изменит емкость, но
сократит

Ответы:


120

Все зависит от того, что вы понимаете под словом «ясный». Один из верных, безусловно, таков:

slice = slice[:0]

Но есть загвоздка. Если элементы среза относятся к типу T:

var slice []T

затем принуждение len(slice)к нулю с помощью вышеупомянутой "уловки" не делает ни одного элемента

slice[:cap(slice)]

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


1
Интересный. Есть ли другой способ удалить все элементы из базового массива среза, оставив при этом базовую емкость без изменений?
Крис Вебер

3
@ChrisWeber: просто выполните итерацию по базовому массиву и установите для всех элементов новое значение
newacct

2
@jnml, я хочу повторно использовать срез (и базовое хранилище массива), поэтому я не выделяю постоянно новый срез (с массивом). Я отредактировал свой вопрос, чтобы пояснить и показать пример кода из стандартной библиотеки.
Крис Вебер

1
Я новичок в Go. Не могли бы вы подробнее объяснить, почему это может быть оптимальный подход? Заранее спасибо.
satoru

Вы уверены, что сброс размера среза вызывает утечку памяти? Я не могу воспроизвести это
Томмазо Барбугли

197

Установка среза nil- лучший способ очистить срез. nilсрезы в go работают отлично, и установка среза nilвысвобождает базовую память сборщику мусора.

Посмотреть детскую площадку

package main

import (
    "fmt"
)

func dump(letters []string) {
    fmt.Println("letters = ", letters)
    fmt.Println(cap(letters))
    fmt.Println(len(letters))
    for i := range letters {
        fmt.Println(i, letters[i])
    }
}

func main() {
    letters := []string{"a", "b", "c", "d"}
    dump(letters)
    // clear the slice
    letters = nil
    dump(letters)
    // add stuff back to it
    letters = append(letters, "e")
    dump(letters)
}

Печать

letters =  [a b c d]
4
4
0 a
1 b
2 c
3 d
letters =  []
0
0
letters =  [e]
1
1
0 e

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

Однако этот метод изменяет емкость на ноль.


Ник спасибо за ответ. Пожалуйста, посмотрите мое обновление. Я очищаю срез для повторного использования. Так что я не обязательно хочу, чтобы базовая память была передана в сборщик мусора, поскольку мне просто нужно выделить ее снова.
Крис Вебер

это то, что я искал!)
Тимур Файзрахманов

5
На основе заголовка «Как очистить кусочек в Go?» это, безусловно, более безопасный ответ, и он должен быть принятым. Идеальным ответом была бы комбинация первоначально принятого ответа и этого, чтобы люди могли решить сами.
Шадонинджа,

1
appending to a nilslice всегда работал в Go?
alediaferia

@alediaferia с тех пор, конечно, 1.0.
Ник Крейг-Вуд,

4

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

Для практики я устроил небольшую площадку для игр: https://play.golang.org/p/9i4gPx3lnY

что соответствует этому:

package main

import "fmt"

type Blah struct {
    babyKitten int
    kittenSays *string
}

func main() {
    meow := "meow"
    Blahs := []Blah{}
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{2, &meow})
    fmt.Printf("Blahs: %v\n", Blahs)
    //fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
    Blahs = nil
    meow2 := "nyan"
    fmt.Printf("Blahs: %v\n", Blahs)
    Blahs = append(Blahs, Blah{1, &meow2})
    fmt.Printf("Blahs: %v\n", Blahs)
    fmt.Printf("kittenSays: %v\n", *Blahs[0].kittenSays)
}

Запуск этого кода как есть покажет один и тот же адрес памяти для переменных «meow» и «meow2» как одинаковые:

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
Blahs: []
Blahs: [{1 0x1030e0f0}]
kittenSays: nyan

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

Blahs: []
Blahs: [{1 0x1030e0c0}]
Blahs: [{1 0x1030e0c0} {2 0x1030e0c0}]
kittenSays: meow
Blahs: []
Blahs: [{1 0x1030e0f8}]
kittenSays: nyan

Я думаю, это может быть из-за того, что печать каким-то образом откладывается (?), Но интересная иллюстрация некоторого поведения управления памятью и еще один голос за:

[]MyStruct = nil

Хорошие подробные примеры. Спасибо!
Доланор 07

2
Это не показывает, что адреса памяти meo1 и meow2 совпадают: 0x1030e0c0не равно 0x1030e0f0(первое заканчивается на c0, второе - на f0).
карбокатион

Должен согласиться с @carbocation здесь, эти адреса памяти не совпадают. Я не претендую на то, чтобы лучше объяснить, что здесь происходит, но это не служит для меня доказательством. Я действительно вижу такое же 8-байтовое несоответствие в адресах meow2каждого запуска ...
rbrtl 01
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.