È sicuro rimuovere le chiavi selezionate dalla mappa all'interno di un loop di intervallo?


135

Come si possono rimuovere le chiavi selezionate da una mappa? È sicuro combinare delete()con range, come nel codice qui sotto?

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

Risposte:


174

Questo è sicuro! Puoi anche trovare un esempio simile in Effective Go :

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

E le specifiche della lingua :

L'ordine di iterazione sulle mappe non è specificato e non è garantito che sia lo stesso da una iterazione alla successiva. Se le voci della mappa che non sono state ancora raggiunte vengono rimosse durante l'iterazione , i corrispondenti valori di iterazione non verranno prodotti. Se le voci della mappa vengono create durante l'iterazione , tale voce può essere prodotta durante l'iterazione o può essere saltata. La scelta può variare per ciascuna voce creata e da una iterazione alla successiva. Se la mappa è nulla, il numero di iterazioni è 0.


key.expired non definito (la stringa di tipo non ha campo o metodo scaduto)

4
@kristen: nell'esempio sopra descritto, la chiave non dovrebbe essere una stringa ma piuttosto un tipo personalizzato che implementa l' func (a T) expired() boolinterfaccia. Ai fini di questo esempio, è possibile provare: 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,

Molto confuso.
g10guang,

150

La risposta di Sebastian è accurata, ma volevo sapere perché era sicura, quindi ho fatto qualche ricerca nel codice sorgente della mappa . Sembra su una chiamata a delete(k, v), in pratica imposta solo un flag (oltre a cambiare il valore del conteggio) invece di eliminare effettivamente il valore:

b->tophash[i] = Empty;

(Vuoto è una costante per il valore 0)

Ciò che la mappa sembra effettivamente fare è l'allocazione di un determinato numero di bucket in base alla dimensione della mappa, che aumenta man mano che si eseguono inserti al ritmo di 2^B(da questo codice sorgente ):

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

Quindi ci sono quasi sempre più bucket allocati di quelli che stai usando e quando fai un rangeover sulla mappa, controlla quel tophashvalore di ogni bucket in quello 2^Bper vedere se può saltarci sopra.

Riassumendo, l' deleteinterno arange è sicuro perché i dati sono tecnicamente ancora lì, ma quando li controlla tophashvede che può semplicemente ignorarli e non includerli in qualsiasi rangeoperazione tu stia eseguendo. Il codice sorgente include anche un TODO:

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

Questo spiega perché l'uso della delete(k,v)funzione in realtà non libera memoria, la rimuove semplicemente dall'elenco dei bucket a cui è consentito accedere. Se vuoi liberare la memoria effettiva dovrai rendere irraggiungibile l'intera mappa in modo che la raccolta dei rifiuti possa intervenire. Puoi farlo usando una linea come

map = nil

2
Quindi sembra che tu stia dicendo che è sicuro eliminare qualsiasi valore arbitrario dalla mappa, non solo quello "attuale", giusto? E quando arriva il momento di valutare un hash che ho precedentemente eliminato in modo arbitrario, lo salterà in modo sicuro?
Flimzy,

@Flimzy È corretto, come puoi vedere da questo parco giochi play.golang.org/p/FwbsghzrsO . Nota che se l'indice che elimini è il primo dell'intervallo, mostrerà comunque quello poiché è già scritto in k, v ma se imposti l'indice su qualsiasi oltre al primo che trova l'intervallo, visualizzerà solo due chiavi / value invece di tre e non panico.
Verran,

1
Il "non libera memoria" è ancora rilevante? Ho cercato di trovare nella fonte quel commento ma non riesco a trovarlo.
Tony,

11
Nota importante: ricorda che questa è solo l' implementazione corrente e potrebbe cambiare in futuro, quindi non devi fare affidamento su proprietà aggiuntive che potrebbero sembrare "supportate". Le uniche garanzie che hai sono quelle fornite dalle specifiche, come descritto nella risposta di Sebastian . (Detto questo, esplorare e spiegare gli interni di Go è sicuramente interessante, istruttivo e generalmente fantastico!)
Akavel

4

Mi chiedevo se potesse verificarsi una perdita di memoria. Quindi ho scritto un programma di test:

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

Sembra che GC libera la memoria. Quindi va bene.


0

Insomma sì. Vedi le risposte precedenti.

E anche questo, da qui :

ianlancetaylor ha commentato il 18 febbraio 2015
Penso che la chiave per capirlo sia rendersi conto che mentre si esegue il corpo di un'istruzione for / range, non esiste alcuna iterazione corrente. C'è un insieme di valori che sono stati visti e un insieme di valori che non sono stati visti. Durante l'esecuzione del corpo, una delle coppie chiave / valore che è stata vista - la coppia più recente - è stata assegnata alle variabili dell'istruzione range. Non c'è niente di speciale in quella coppia chiave / valore, è solo uno di quelli che sono già stati visti durante l'iterazione.

La domanda a cui sta rispondendo riguarda la modifica degli elementi della mappa in atto durante rangeun'operazione, motivo per cui menziona l '"iterazione corrente". Ma è anche rilevante qui: puoi eliminare le chiavi durante un intervallo, e questo significa solo che non le vedrai più avanti nell'intervallo (e se le hai già viste, va bene).

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.