从范围循环内的Golang映射中删除选定的键是否安全?

如何从Golang地图中删除选定的密钥? 在下面的代码中将delete()和range结合起来是否安全?

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

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

这是安全的! 您也可以在Effective Go中find类似的示例:

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

和语言规范 :

地图上的迭代次序没有被指定,从一次迭代到下一次迭代不能保证是相同的。 如果在迭代过程移除尚未到达的映射条目,则不会生成相应的迭代值。 如果迭代过程创build映射条目,那么可能会在迭代过程中生成该条目,或者可能会跳过该条目。 每个条目的select可能会有所不同,从一个迭代到下一个迭代。 如果映射为零,则迭代次数为0。

塞巴斯蒂安的答案是准确的,但我想知道为什么它是安全的,所以我做了一些挖掘地图的源代码 。 它看起来像delete(k, v)的调用,它基本上只是设置一个标志(以及更改计数值),而不是实际删除值:

 b->tophash[i] = Empty; 

(空值是0的常数)

地图看起来实际上做的是根据地图的大小来分配一定数量的存储桶,当您以2^B的速率执行插入时(从该源代码 ),存储容量将会增长。

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

所以几乎总是有比你使用的更多的桶分配,当你在地图上做一个range的时候,它会检查那个2^B中每个桶的tophash值,看它是否可以跳过它。

总而言之, rangedelete是安全的,因为数据在技术上仍然存在,但是当它检查到tophash时,它会看到它可以跳过它,而不是将它包含在你正在执行的任何range操作中。 源代码甚至包括一个TODO

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

这就解释了为什么使用delete(k,v)函数实际上并没有释放内存,只是将其从允许访问的桶列表中删除。 如果你想释放实际的内存,你需要使整个映射无法访问,这样垃圾收集就会进入。你可以使用像

 map = nil 

我想知道是否会发生内存泄漏。 于是我写了一个testing程序:

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

看起来像GC可以释放内存。 所以没关系