Bir menzil döngüsü içinde seçilen anahtarları haritadan kaldırmak güvenli midir?


136

Bir haritadan seçilen anahtarlar nasıl kaldırılabilir? delete()Aşağıdaki kodda olduğu gibi, aralık ile birleştirmek güvenli midir?

package main

import "fmt"

type Info struct {
    value string
}

func main() {
    table := make(map[string]*Info)

    for i := 0; i < 10; i++ {
        str := fmt.Sprintf("%v", i)
        table[str] = &Info{str}
    }

    for key, value := range table {
        fmt.Printf("deleting %v=>%v\n", key, value.value)
        delete(table, key)
    }
}

https://play.golang.org/p/u1vufvEjSw

Yanıtlar:


175

Bu güvenlidir! Benzer bir örneği Effective Go'da da bulabilirsiniz :

for key := range m {
    if key.expired() {
        delete(m, key)
    }
}

Ve dil özelliği :

Haritalar üzerindeki yineleme sırası belirtilmemiştir ve bir yinelemeden diğerine aynı olacağı garanti edilmez. Henüz ulaşılmamış harita girişleri yineleme sırasında kaldırılırsa , ilgili yineleme değerleri üretilmeyecektir. Yineleme sırasında harita girdileri oluşturulursa , bu girdi yineleme sırasında üretilebilir veya atlanabilir. Seçim, oluşturulan her giriş için ve bir yinelemeden diğerine değişebilir. Harita sıfırsa, yineleme sayısı 0'dır.


key.expired tanımlanmamış (tip dizesi alanı vardır veya yöntem süresi)

4
@kristen - yukarıda açıklanan örnekte, anahtar bir dizge olmamalı, bunun yerine func (a T) expired() boolarayüzü uygulayan özel bir tür olmalıdır . Bu örneğin amaçları doğrultusunda şunları deneyebilirsiniz: m := make(map[int]int) /* populate m here somehow */ for key := range (m) { if key % 2 == 0 { /* this is just some condition, such as calling expired */ delete(m, key); } }
abanana

Çok kafa karıştırıcı.
g10guang

150

Sebastian'ın cevabı doğru, ancak neden güvenli olduğunu bilmek istedim , bu yüzden Harita kaynak kodunu biraz araştırdım . Bir çağrıda göründüğü gibi, delete(k, v)değeri gerçekten silmek yerine temelde sadece bir bayrak belirler (sayma değerini değiştirmenin yanı sıra):

b->tophash[i] = Empty;

(Boş, değer için sabittir 0)

Haritanın gerçekte yaptığı şey, haritanın boyutuna bağlı olarak, 2^B( bu kaynak koddan ) oranında ekleme gerçekleştirdikçe büyüyen belirli sayıda kova ayırmaktır :

byte    *buckets;     // array of 2^B Buckets. may be nil if count==0.

Dolayısıyla, neredeyse her zaman kullandığınızdan daha fazla kova tahsis edilir rangeve harita üzerinde bir yaptığınızda tophash, her bir bölümün değerini 2^B, atlayıp atlayamayacağını görmek için kontrol eder .

Özetlemek gerekirse, a'nın deleteiçi rangegüvenlidir, çünkü veriler teknik olarak hala oradadır, ancak kontrol ettiğinde, tophashonu atlayabileceğini ve rangegerçekleştirdiğiniz işleme dahil etmeyebileceğini görür . Kaynak kodu şunları da içerir TODO:

 // TODO: consolidate buckets if they are mostly empty
 // can only consolidate if there are no live iterators at this size.

Bu, delete(k,v)işlevi kullanmanın neden hafızayı gerçekten boşaltmadığını, yalnızca onu erişmenize izin verilen paketler listesinden kaldırdığını açıklar . Eğer gerçek hafızayı boşaltmak istiyorsanız, çöp toplama işleminin devreye girmesi için tüm haritayı erişilemez hale getirmeniz gerekir. Bunu aşağıdaki gibi bir satır kullanarak yapabilirsiniz:

map = nil

2
Öyleyse, haritadan herhangi bir keyfi değeri silmenin güvenli olduğunu söylüyorsunuz, sadece 'mevcut' olanı değil, değil mi? Ve daha önce keyfi olarak sildiğim bir hash'i değerlendirme zamanı geldiğinde, güvenle atlayacak mı?
Flimzy

@Flimzy Bu oyun parkından da görebileceğiniz gibi bu doğru . Play.golang.org/p/FwbsghzrsO . Sildiğiniz dizin, aralıktaki ilk dizin ise, zaten k, v'ye yazıldığı için bunu göstermeye devam edeceğini, ancak dizini ilkinden başka herhangi birine ayarlarsanız, aralığın yalnızca iki anahtar göstereceğini unutmayın. / üç yerine değer çifti ve panik değil.
Verran

1
"Aslında hafızayı boşaltmaz" hala geçerli mi? Bu yorumu kaynakta bulmaya çalıştım ama bulamadım.
Tony

11
Önemli not: Bunun sadece mevcut uygulama olduğunu ve gelecekte değişebileceğini unutmayın, bu nedenle "destek" olarak görünebilecek ek özelliklere güvenmemelisiniz. Sahip olduğunuz tek teminat şartname tarafından sağlanan vardır Sebastian'ın cevapta açıklandığı gibi . (Bununla birlikte, Go'nun iç
kısımlarını

4

Hafıza sızıntısı olup olmadığını merak ediyordum. Bu yüzden bir test programı yazdım:

package main

import (
    log "github.com/Sirupsen/logrus"
    "os/signal"
    "os"
    "math/rand"
    "time"
)

func main() {
    log.Info("=== START ===")
    defer func() { log.Info("=== DONE ===") }()

    go func() {
        m := make(map[string]string)
        for {
            k := GenerateRandStr(1024)
            m[k] = GenerateRandStr(1024*1024)

            for k2, _ := range m {
                delete(m, k2)
                break
            }
        }
    }()

    osSignals := make(chan os.Signal, 1)
    signal.Notify(osSignals, os.Interrupt)
    for {
        select {
        case <-osSignals:
            log.Info("Recieved ^C command. Exit")
            return
        }
    }
}

func GenerateRandStr(n int) string {
    rand.Seed(time.Now().UnixNano())
    const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    b := make([]byte, n)
    for i := range b {
        b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
    }
    return string(b)
}

Görünüşe göre GC do hafızayı serbest bırakıyor. Yani sorun değil.


0

Kısaca evet. Önceki cevaplara bakın.

Ve ayrıca bu, buradan :

ianlancetaylor 18 Şubat 2015'te yorum yaptı
Bunu anlamanın anahtarı, bir for / range ifadesinin gövdesini çalıştırırken mevcut bir yineleme olmadığını anlamaktır. Görülmüş bir dizi değer ve görülmemiş bir dizi değer vardır. Gövde yürütülürken, görülen anahtar / değer çiftlerinden biri - en son çift - range ifadesinin değişken (ler) ine atandı. Bu anahtar / değer çifti hakkında özel bir şey yok, yineleme sırasında zaten görülmüş olanlardan sadece biri.

Cevapladığı soru, bir rangeoperasyon sırasında harita öğelerini yerinde değiştirmekle ilgili , bu yüzden "mevcut yinelemeden" bahsediyor. Ama burada da geçerli: Bir aralıkta anahtarları silebilirsiniz ve bu, onları daha sonra aralıkta görmeyeceğiniz anlamına gelir (ve eğer onları zaten görmüşseniz, sorun değil).

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.