Go'da bir dilimi nasıl temizlersiniz?


125

Go'da bir dilimi temizlemenin uygun yolu nedir?

İşte go forumlarında bulduklarım :

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

Bu doğru mu?

Açıklığa kavuşturmak için, yeniden kullanılabilmesi için tampon temizlenir.

Bir örnek, bayt paketindeki Buffer.Truncate işlevidir.

Sıfırlamanın yalnızca Truncate (0) olarak adlandırdığına dikkat edin. Öyleyse, bu durumda 70. satırın şunu değerlendireceği anlaşılıyor: 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
Hızlı bir test: play.golang.org/p/6Z-qDQtpbg bunun işe yarayacağını gösteriyor gibi görünüyor (kapasiteyi değiştirmeyecek, ancak uzunluğu kısaltacak)
Jason Sperske

Yanıtlar:


120

Her şey sizin 'net' tanımınızın ne olduğuna bağlıdır. Geçerli olanlardan biri kesinlikle:

slice = slice[:0]

Ama bir sorun var. Dilim elemanları T tipindeyse:

var slice []T

Sonra zorlama len(slice)"hile" Yukarıda tarafından, sıfır olduğu için değil hiçbir öğesini yapmak

slice[:cap(slice)]

çöp toplama için uygun. Bu, bazı senaryolarda en uygun yaklaşım olabilir. Ancak "bellek sızıntılarının" bir nedeni de olabilir - bellek kullanılmaz, ancak potansiyel olarak erişilebilirdir ("dilim" yeniden dilimlendikten sonra) ve dolayısıyla "toplanamaz" çöp değildir.


1
İlginç. Temel kapasiteyi değiştirmeden bırakırken, tüm öğeleri dilimin temelindeki diziden kaldırmanın başka bir yolu var mı?
Chris Weber

3
@ChrisWeber: sadece alttaki diziyi yineleyin ve tüm öğeleri yeni bir değere ayarlayın
newacct

2
@jnml, dilimi (ve temel alınan dizi depolamasını) yeniden kullanmak istiyorum, bu nedenle sürekli olarak yeni bir dilim (dizi ile) ayırmıyorum. Standart kitaplıktan bazı örnek kodları açıklığa kavuşturmak ve göstermek için sorumu düzenledim.
Chris Weber

1
Go'da yeniyim. Bunun neden en uygun yaklaşım olabileceği konusunda daha fazla açıklama yapabilir misiniz lütfen? Şimdiden teşekkürler.
satoru

Dilim boyutu sıfırlamanın bellek sızıntılarına neden olduğundan emin misiniz? Onu yeniden
üretemiyorum

197

Dilimi olarak ayarlamak, nilbir dilimi temizlemenin en iyi yoludur. nilhareket halindeki dilimler mükemmel şekilde davranır ve dilimi olarak ayarlamak nil, temeldeki belleği çöp toplayıcıya serbest bırakır.

Oyun alanına bakın

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

Baskılar

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

İki dilim aynı temel belleğe işaret edecek şekilde dilimlere kolayca diğer ad verilebileceğini unutmayın. Ayar, nilbu takma adı kaldıracaktır.

Bu yöntem, kapasiteyi sıfıra değiştirir.


Nick cevap için teşekkürler. Lütfen güncellememe bakın. Dilimi yeniden kullanım için temizliyorum. Bu nedenle, temel belleğin GC'ye bırakılmasını istemiyorum, çünkü onu tekrar tahsis etmem gerekecek.
Chris Weber

aradım!)
Timur Fayzrakhmanov

5
"Go'da bir dilimi nasıl temizlersiniz?" Başlığına göre bu uzak ara daha güvenli cevaptır ve kabul edilen cevap olmalıdır. Mükemmel bir cevap, başlangıçta kabul edilen cevap ile bunun birleşimi olabilir, böylece insanlar kendi başlarına karar verebilirler.
Shadoninja

1
appendBir nildilim yapmak her zaman Go'da işe yaradı mı?
alediaferia

@alediaferia beri 1.0 kesinlikle gidin.
Nick Craig-Wood

4

Bu konuyu biraz kendi amaçlarım için inceliyordum; Bir parça yapım vardı (bazı işaretçiler dahil) ve doğru anladığımdan emin olmak istedim; bu ileti dizisinde sona erdi ve sonuçlarımı paylaşmak istedim.

Pratik yapmak için biraz oyun alanı yaptım: https://play.golang.org/p/9i4gPx3lnY

hangisi bunu gösteriyor:

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

Bu kodu olduğu gibi çalıştırmak, hem "miyav" hem de "miyav2" değişkenleri için aynı bellek adresini gösterecektir:

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

ki bence yapının çöp toplandığını doğruluyor. İşin garibi, yorumlanmış baskı satırının yorumunu kaldırmak, miyavlar için farklı bellek adresleri verecektir:

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

Sanırım bu, baskının bir şekilde ertelenmiş olmasından kaynaklanıyor olabilir (?), Ancak bazı hafıza yönetimi davranışlarının ilginç bir örneği ve bir oy daha:

[]MyStruct = nil

Güzel detaylı örnekler. Teşekkürler!
Dolanor

2
Bu meo1 ve meow2'nin hafıza adreslerinin aynı olduğunu göstermez: 0x1030e0c0eşit değildir 0x1030e0f0(birincisi ile biter c0, ikincisi içeride biter f0).
karbokatyon

Burada @carbocation ile aynı fikirde olmalısınız, bu hafıza adresleri aynı değil. Burada neler olduğunu daha iyi açıklayabileceğimi iddia etmiyorum ama bu bana kanıt olarak hizmet etmiyor. meow2Her çalışmanın adreslerinde aynı 8 bayt tutarsızlığı görüyorum ...
rbrtl
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.