Comment effacer une tranche dans Go?

125

Quelle est la méthode appropriée pour effacer une tranche dans Go?

Voici ce que j'ai trouvé dans les forums 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))
}

Est-ce correct?

Pour clarifier, le tampon est effacé afin qu'il puisse être réutilisé.

Un exemple est la fonction Buffer.Truncate dans le package d'octets.

Notez que Reset appelle simplement Truncate (0). Il semble donc que dans ce cas, la ligne 70 évaluerait: 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
la source
1
Un test rapide sur: play.golang.org/p/6Z-qDQtpbg semble suggérer que cela fonctionnera (ne changera pas la capacité mais cela tronquera la longueur)
Jason Sperske

Réponses:

120

Tout dépend de votre définition de «clair». L'un des plus valables est certainement:

slice = slice[:0]

Mais il y a un hic. Si les éléments de tranche sont de type T:

var slice []T

puis imposer len(slice)à zéro, par le "truc" ci-dessus, ne fait aucun élément de

slice[:cap(slice)]

éligible pour la collecte des ordures. Cela pourrait être l'approche optimale dans certains scénarios. Mais cela peut aussi être une cause de "fuites de mémoire" - mémoire non utilisée, mais potentiellement accessible (après re-slicing de 'slice') et donc pas de déchets "collectable".

zzzz
la source
1
Intéressant. Existe-t-il un autre moyen de supprimer tous les éléments du tableau sous-jacent de la tranche tout en laissant la capacité sous-jacente inchangée?
Chris Weber
3
@ChrisWeber: parcourez simplement le tableau sous-jacent et définissez tous les éléments sur une nouvelle valeur
newacct
2
@jnml, je veux réutiliser la tranche (et le stockage de tableau sous-jacent) afin de ne pas allouer constamment une nouvelle tranche (avec tableau). J'ai édité ma question pour clarifier et montrer un exemple de code de la bibliothèque standard.
Chris Weber
1
Je suis nouveau sur Go. Pourriez-vous s'il vous plaît expliquer pourquoi cela peut être une approche optimale s'il vous plaît? Merci d'avance.
satoru
Êtes-vous sûr que la réinitialisation de la taille de la tranche provoque des fuites de mémoire? Je ne suis pas en mesure de le reproduire
Tommaso Barbugli
197

Définir la tranche sur nilest la meilleure façon d'effacer une tranche. nilles tranches dans go se comportent parfaitement et la définition de la tranche sur nilva libérer la mémoire sous-jacente vers le ramasse-miettes.

Voir aire de jeux

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

Tirages

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

Notez que les tranches peuvent facilement être aliasées de sorte que deux tranches pointent vers la même mémoire sous-jacente. Le paramètre sur nilsupprimera cet alias.

Cette méthode modifie la capacité à zéro.

Nick Craig-Wood
la source
Nick merci de la réponse. S'il vous plaît voir ma mise à jour vous le feriez. Je nettoie la tranche pour la réutiliser. Je ne veux donc pas nécessairement que la mémoire sous-jacente soit libérée dans le GC car je devrai simplement l'allouer à nouveau.
Chris Weber
c'est ce que j'ai cherché!)
Timur Fayzrakhmanov
5
Basé sur le titre "Comment effacer une tranche dans Go?" c'est de loin la réponse la plus sûre et devrait être acceptée. Une réponse parfaite serait la combinaison de la réponse initialement acceptée et de celle-ci afin que les gens puissent décider eux-mêmes, cependant.
Shadoninja
1
appending to a nilslice a toujours fonctionné en Go?
alediaferia
@alediaferia depuis la version 1.0 certainement.
Nick Craig-Wood
4

J'étudiais un peu cette question à mes propres fins; J'avais une tranche de structures (y compris des pointeurs) et je voulais m'assurer de bien faire les choses; s'est retrouvé sur ce fil et a voulu partager mes résultats.

Pour pratiquer, j'ai fait un petit terrain de jeu: https://play.golang.org/p/9i4gPx3lnY

qui évalue à ceci:

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'exécution de ce code tel quel affichera la même adresse mémoire pour les variables "meow" et "meow2" comme étant la même:

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

qui, je pense, confirme que la structure est garbage collection. Curieusement, décommenter la ligne d'impression commentée donnera des adresses mémoire différentes pour les miaulements:

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

Je pense que cela peut être dû au fait que l'impression a été différée d'une manière ou d'une autre (?), Mais une illustration intéressante d'un certain comportement de gestion de la mémoire, et un vote de plus pour:

[]MyStruct = nil
max garvey
la source
Beaux exemples détaillés. Merci!
Dolanor
2
Cela ne montre pas que les adresses mémoire de meo1 et meow2 sont identiques: 0x1030e0c0n'est pas égal à 0x1030e0f0(le premier se termine par c0, le second en f0).
carbocation
Je suis d'accord avec @carbocation ici, ces adresses mémoire ne sont pas les mêmes. Je ne prétends pas pouvoir mieux expliquer ce qui se passe ici, mais cela ne me sert pas de preuve. Je vois la même différence de 8 octets dans les adresses de meow2chaque exécution ...
rbrtl