从范围循环内的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
值,看它是否可以跳过它。
总而言之, range
的delete
是安全的,因为数据在技术上仍然存在,但是当它检查到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可以释放内存。 所以没关系