Come si cancella una fetta in Go?


125

Qual è il modo appropriato per cancellare una porzione in Go?

Ecco cosa ho trovato nei forum 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))
}

È corretto?

Per chiarire, il buffer viene cancellato in modo che possa essere riutilizzato.

Un esempio è la funzione Buffer.Truncate nel pacchetto bytes.

Notare che Reset chiama solo Truncate (0). Quindi sembra che in questo caso la riga 70 valuterà: 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
Un rapido test su: play.golang.org/p/6Z-qDQtpbg sembra suggerire che funzionerà (non cambierà la capacità ma
troncerà

Risposte:


120

Tutto dipende da quale è la tua definizione di "chiaro". Uno di quelli validi è sicuramente:

slice = slice[:0]

Ma c'è un problema. Se gli elementi slice sono di tipo T:

var slice []T

quindi forzare len(slice)a essere zero, con il "trucco" di cui sopra, non fa alcun elemento di

slice[:cap(slice)]

idoneo per la raccolta dei rifiuti. Questo potrebbe essere l'approccio ottimale in alcuni scenari. Ma potrebbe anche essere una causa di "memory leak" - memoria non utilizzata, ma potenzialmente raggiungibile (dopo il re-slicing di "slice") e quindi non "garbage collectable".


1
Interessante. Esiste un altro modo per rimuovere tutti gli elementi dall'array sottostante della slice lasciando invariata la capacità sottostante?
Chris Weber

3
@ChrisWeber: basta scorrere l'array sottostante e impostare tutti gli elementi su un nuovo valore
newacct

2
@jnml, voglio riutilizzare la fetta (e l'archiviazione dell'array sottostante), quindi non alloco costantemente una nuova fetta (con array). Ho modificato la mia domanda per chiarire e mostrare un codice di esempio dalla libreria standard.
Chris Weber

1
Sono nuovo su Go. Potresti spiegare di più sul motivo per cui questo può essere un approccio ottimale, per favore? Grazie in anticipo.
satoru

Sei sicuro che il ripristino delle dimensioni della slice causi perdite di memoria? Non sono in grado di riprodurlo
Tommaso Barbugli

197

L'impostazione della fetta su nilè il modo migliore per cancellare una fetta. nille slice in go si comportano perfettamente e impostando la slice su nilrilascerà la memoria sottostante al garbage collector.

Vedi playground

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

stampe

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

Si noti che le slice possono essere facilmente sottoposte ad alias in modo che due slice puntino alla stessa memoria sottostante. L'impostazione su nilrimuoverà l'aliasing.

Questo metodo cambia la capacità a zero però.


Nick grazie della risposta. Si prega di consultare il mio aggiornamento, lo faresti. Sto cancellando la fetta per il riutilizzo. Quindi non voglio necessariamente che la memoria sottostante venga rilasciata al GC poiché dovrò solo allocarla di nuovo.
Chris Weber

è quello che ho cercato!)
Timur Fayzrakhmanov

5
Basato sul titolo "Come si cancella una porzione in Go?" questa è di gran lunga la risposta più sicura e dovrebbe essere quella accettata. Una risposta perfetta sarebbe la combinazione della risposta originariamente accettata e questa in modo che le persone possano decidere da sole, però.
Shadoninja

1
appending to a nilslice ha sempre funzionato in Go?
alediaferia

@alediaferia da allora passa sicuramente alla 1.0.
Nick Craig-Wood,

4

Stavo esaminando un po 'questo problema per i miei scopi; Avevo una parte di strutture (inclusi alcuni puntatori) e volevo assicurarmi di aver capito bene; finito su questo thread e volevo condividere i miei risultati.

Per esercitarmi, ho fatto un piccolo go playground: https://play.golang.org/p/9i4gPx3lnY

che corrisponde a questo:

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

L'esecuzione di quel codice così com'è mostrerà lo stesso indirizzo di memoria per entrambe le variabili "meow" e "meow2" come se fossero le stesse:

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

che penso confermi che la struttura è stata raccolta da rifiuti. Stranamente, rimuovere il commento dalla riga di stampa commentata produrrà diversi indirizzi di memoria per i miagolii:

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

Penso che ciò possa essere dovuto al fatto che la stampa è stata differita in qualche modo (?), Ma illustrazione interessante di alcuni comportamenti della memoria mgmt, e un altro voto per:

[]MyStruct = nil

Bei esempi dettagliati. Grazie!
Dolanor

2
Questo non mostra che gli indirizzi di memoria di meo1 e meow2 sono uguali: 0x1030e0c0non è uguale a 0x1030e0f0(il primo termina con c0, il secondo con f0).
carbocatione

Devo essere d'accordo con @carbocation qui, quegli indirizzi di memoria non sono gli stessi. Non pretendo di essere in grado di spiegare meglio cosa sta succedendo qui, ma questo non serve come prova per me. Vedo la stessa discrepanza di 8 byte negli indirizzi di meow2ogni esecuzione ...
rbrtl
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.