Wie löscht man eine Scheibe in Go?

124

Was ist der geeignete Weg, um ein Slice in Go zu löschen?

Folgendes habe ich in den Go-Foren gefunden :

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

Ist das richtig?

Zur Verdeutlichung wird der Puffer gelöscht, damit er wiederverwendet werden kann.

Ein Beispiel ist die Funktion Buffer.Truncate im Byte-Paket.

Beachten Sie, dass Reset nur Truncate (0) aufruft. Es scheint also, dass in diesem Fall Zeile 70 Folgendes auswerten würde: 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
quelle
1
Ein kurzer Test auf: play.golang.org/p/6Z-qDQtpbg scheint darauf hinzudeuten , dass es funktionieren wird (ändert nicht die Kapazität, aber es wird die Länge abschneiden)
Jason Sperske

Antworten:

119

Es hängt alles davon ab, wie Sie "klar" definieren. Eine der gültigen ist sicherlich:

slice = slice[:0]

Aber da ist ein Fang. Wenn Slice-Elemente vom Typ T sind:

var slice []T

dann Durchsetzung len(slice)Null zu sein, durch die obigen „Trick“, nicht macht jedes Element von

slice[:cap(slice)]

berechtigt zur Müllabfuhr. Dies kann in einigen Szenarien der optimale Ansatz sein. Es kann aber auch eine Ursache für "Speicherlecks" sein - Speicher wird nicht verwendet, ist aber möglicherweise erreichbar (nach erneutem Schneiden von "Slice") und daher nicht "sammelbar".

zzzz
quelle
1
Interessant. Gibt es eine andere Möglichkeit, alle Elemente aus dem zugrunde liegenden Array des Slice zu entfernen, während die zugrunde liegende Kapazität unverändert bleibt?
Chris Weber
3
@ ChrisWeber: iterieren Sie einfach über das zugrunde liegende Array und setzen Sie alle Elemente auf einen neuen Wert
newacct
2
@jnml, ich möchte das Slice (und den zugrunde liegenden Array-Speicher) wiederverwenden, damit ich nicht ständig ein neues Slice (mit Array) zuordne. Ich habe meine Frage bearbeitet, um einen Beispielcode aus der Standardbibliothek zu verdeutlichen und anzuzeigen.
Chris Weber
1
Ich bin neu zu gehen. Könnten Sie bitte näher erläutern, warum dies ein optimaler Ansatz sein kann? Danke im Voraus.
Satoru
Sind Sie sicher, dass das Zurücksetzen der Slice-Größe zu Speicherlecks führt? Ich kann es nicht reproduzieren
Tommaso Barbugli
196

Das Festlegen der Scheibe nilist der beste Weg, um eine Scheibe zu löschen. nilSlices in Go verhalten sich perfekt und wenn Sie das Slice so nileinstellen , wird der zugrunde liegende Speicher für den Garbage Collector freigegeben.

Siehe Spielplatz

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

Druckt

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

Beachten Sie, dass Slices leicht mit einem Alias ​​versehen werden können, sodass zwei Slices auf denselben zugrunde liegenden Speicher verweisen. Die Einstellung zu nilentfernt dieses Aliasing.

Diese Methode ändert die Kapazität jedoch auf Null.

Nick Craig-Wood
quelle
Nick, danke für die Antwort. Bitte sehen Sie mein Update, das Sie würden. Ich räume die Scheibe zur Wiederverwendung frei. Ich möchte also nicht unbedingt, dass der zugrunde liegende Speicher für den GC freigegeben wird, da ich ihn nur erneut zuweisen muss.
Chris Weber
es ist das, wonach ich gesucht habe!)
Timur Fayzrakhmanov
5
Basierend auf dem Titel "Wie löscht man ein Slice in Go?" Dies ist bei weitem die sicherere Antwort und sollte die akzeptierte sein. Eine perfekte Antwort wäre die Kombination der ursprünglich akzeptierten Antwort und dieser, damit die Leute selbst entscheiden können.
Shadoninja
1
appendnilIn Go hat immer ein Slice funktioniert?
Alediaferia
@alediaferia seitdem gehen 1.0 sicher.
Nick Craig-Wood
4

Ich habe mich ein wenig mit diesem Thema für meine eigenen Zwecke befasst. Ich hatte eine Reihe von Strukturen (einschließlich einiger Zeiger) und wollte sicherstellen, dass ich alles richtig gemacht habe. landete in diesem Thread und wollte meine Ergebnisse teilen.

Zum Üben habe ich einen kleinen Spielplatz besucht: https://play.golang.org/p/9i4gPx3lnY

was dazu führt:

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

Wenn Sie diesen Code so wie er ist ausführen, wird dieselbe Speicheradresse für die Variablen "meow" und "meow2" als identisch angezeigt:

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

was meiner Meinung nach bestätigt, dass die Struktur Müll gesammelt ist. Seltsamerweise ergibt das Kommentieren der kommentierten Druckzeile unterschiedliche Speicheradressen für die Miauen:

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

Ich denke, dies kann daran liegen, dass der Druck auf irgendeine Weise verschoben wurde (?), Aber eine interessante Illustration eines bestimmten Speicherverhaltens und eine weitere Stimme für:

[]MyStruct = nil
max garvey
quelle
Schöne detaillierte Beispiele. Vielen Dank!
Dolanor
2
Dies zeigt nicht, dass die Speicheradressen von meo1 und meow2 gleich sind: 0x1030e0c0ist nicht gleich 0x1030e0f0(ersteres endet in c0, letzteres in f0).
Carbokation
Ich muss hier mit @carbocation übereinstimmen, diese Speicheradressen sind nicht gleich. Ich behaupte nicht, besser erklären zu können, was hier vor sich geht, aber das dient mir nicht als Beweis. Ich sehe die gleiche 8-Byte-Diskrepanz in den Adressen meow2jedes Laufs ...
rbrtl