Como você limpa uma fatia no Go?

125

Qual é a maneira apropriada de limpar uma fatia no Go?

Aqui está o que eu encontrei nos fóruns 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))
}

Isso está correto?

Para esclarecer, o buffer é limpo para que possa ser reutilizado.

Um exemplo é a função Buffer.Truncate no pacote de bytes.

Observe que a redefinição apenas chama Truncate (0). Portanto, parece que neste caso a linha 70 avaliaria: 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) }
Chris Weber
fonte
1
Um teste rápido em: play.golang.org/p/6Z-qDQtpbg parece sugerir que ele vai trabalhar (não vai alterar a capacidade, mas ele vai truncar o comprimento)
Jason Sperske

Respostas:

120

Tudo depende de qual é a sua definição de 'claro'. Um dos válidos certamente é:

slice = slice[:0]

Mas há um problema. Se os elementos de fatia forem do tipo T:

var slice []T

forçar len(slice)a zero, pelo "truque" acima, não cria nenhum elemento de

slice[:cap(slice)]

elegíveis para coleta de lixo. Essa pode ser a abordagem ideal em alguns cenários. Mas também pode ser uma causa de "vazamentos de memória" - memória não usada, mas potencialmente acessível (após o re-fatiamento de 'fatia') e, portanto, não "lixo" colecionável.

zzzz
fonte
1
Interessante. Existe alguma outra maneira de remover todos os elementos da matriz subjacente da fatia, mantendo inalterada a capacidade subjacente?
Chris Weber
3
@ ChrisWeber: apenas itere sobre a matriz subjacente e defina todos os elementos para um novo valor
newacct
2
@ jnml, quero reutilizar a fatia (e o armazenamento da matriz subjacente) para não estar constantemente alocando uma nova fatia (com matriz). Editei minha pergunta para esclarecer e mostrar algum código de exemplo da biblioteca padrão.
Chris Weber
1
Eu sou novo no Go. Poderia, por favor, explicar mais sobre por que essa pode ser uma abordagem ideal, por favor? Desde já, obrigado.
Satoru
Tem certeza de que a redefinição do tamanho da fatia causa vazamento de memória? Eu não sou capaz de reproduzi-lo
Tommaso Barbugli
197

Definir a fatia como nilé a melhor maneira de limpar uma fatia. nilas fatias em movimento são perfeitamente bem comportadas e a configuração da fatia nilliberará a memória subjacente para o coletor de lixo.

Ver parque infantil

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)
}

Impressões

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

Observe que as fatias podem ser facilmente alteradas para que duas fatias aponte para a mesma memória subjacente. A configuração para nilremoverá o alias.

Este método altera a capacidade para zero embora.

Nick Craig-Wood
fonte
Nick agradece a resposta. Por favor, veja minha atualização que você faria. Estou limpando a fatia para reutilização. Portanto, não quero necessariamente que a memória subjacente seja liberada para o GC, pois precisarei alocá-la novamente.
Chris Weber
é o que eu procurei)!
Timur Fayzrakhmanov
5
Com base no título "Como você limpa uma fatia no Go?" esta é de longe a resposta mais segura e deve ser a aceita. Uma resposta perfeita seria a combinação da resposta originalmente aceita e essa para que as pessoas possam decidir por si mesmas.
Shadoninja 13/10/2015
1
appendnilsempre uma fatia funcionou no Go?
alediaferia
@alediaferia desde então vá certamente 1.0.
Nick Craig-Wood
4

Eu estava analisando esse problema um pouco para meus próprios propósitos; Eu tinha uma fatia de estruturas (incluindo alguns ponteiros) e queria ter certeza de que estava certo; terminou neste tópico e queria compartilhar meus resultados.

Para praticar, fiz um pequeno playground: https://play.golang.org/p/9i4gPx3lnY

que avalia isso:

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)
}

A execução desse código como está exibirá o mesmo endereço de memória para as variáveis ​​"meow" e "meow2" como sendo o mesmo:

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

o que eu acho que confirma que a estrutura é um lixo coletado. Curiosamente, descomentar a linha de impressão comentada, produzirá endereços de memória diferentes para os miados:

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

Eu acho que isso pode ser devido à impressão ser adiada de alguma forma (?), Mas ilustração interessante de algum comportamento de gerenciamento de memória e mais um voto para:

[]MyStruct = nil
max garvey
fonte
Bons exemplos detalhados. Obrigado!
Dolanor
2
Isso não mostra os endereços de memória de meo1 e meow2 sendo iguais: 0x1030e0c0não é igual a 0x1030e0f0(o primeiro termina em c0, o último em f0).
carbocatião
Tenho que concordar com @carbocation aqui, esses endereços de memória não são os mesmos. Não pretendo explicar melhor o que está acontecendo aqui, mas isso não serve como evidência para mim. Eu vejo a mesma discrepância de 8 bytes nos endereços de meow2cada corrida ...
rbrtl