Eve*_*ton 115 dictionary for-loop go
如何从地图中删除选定的键?delete()与范围结合是否安全,如下面的代码?
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)
}
}
Run Code Online (Sandbox Code Playgroud)
Seb*_*ian 154
这很安全!您还可以在Effective Go中找到类似的示例:
for key := range m {
if key.expired() {
delete(m, key)
}
}
Run Code Online (Sandbox Code Playgroud)
和语言规范:
未指定地图上的迭代顺序,并且不保证从一次迭代到下一次迭代是相同的.如果在迭代期间删除了尚未到达的映射条目,则不会生成相应的迭代值.如果在迭代期间创建了映射条目,则可以在迭代期间生成该条目,或者可以跳过该条目.对于创建的每个条目以及从一次迭代到下一次迭代,选择可能不同.如果映射为nil,则迭代次数为0.
Ver*_*ran 140
Sebastian的答案是准确的,但我想知道为什么它是安全的,所以我做了一些挖掘Map源代码.它看起来像在调用时delete(k, v),它基本上只是设置一个标志(以及更改计数值)而不是实际删除值:
b->tophash[i] = Empty;
Run Code Online (Sandbox Code Playgroud)
(Empty是值的常量0)
地图实际上正在做的是根据地图的大小分配一定数量的存储桶,当您以2^B(来自此源代码)的速率执行插入时,该数据量会增加:
byte *buckets; // array of 2^B Buckets. may be nil if count==0.
Run Code Online (Sandbox Code Playgroud)
因此,几乎总是分配比您正在使用的桶更多的桶,并且当您range在地图上执行时,它会检查其中tophash每个桶的值2^B以查看它是否可以跳过它.
总而言之,deletea range中的数据是安全的,因为数据在技术上仍然存在,但是当它检查时tophash它看到它可以跳过它并且不包括在range你正在执行的任何操作中.源代码甚至包括TODO:
// TODO: consolidate buckets if they are mostly empty
// can only consolidate if there are no live iterators at this size.
Run Code Online (Sandbox Code Playgroud)
这解释了为什么使用该delete(k,v)函数实际上不会释放内存,只需将其从您允许访问的存储区列表中删除即可.如果你想释放实际内存,你需要让整个地图无法访问,这样垃圾收集就会介入.你可以使用像
map = nil
Run Code Online (Sandbox Code Playgroud)
我想知道是否会发生内存泄漏。于是我写了一个测试程序:
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)
}
Run Code Online (Sandbox Code Playgroud)
看起来 GC 确实释放了内存。所以没关系。
简而言之,是的。请参阅之前的答案。
还有这个,从这里开始:
ianlancetaylor 于 2015 年 2 月 18 日发表评论
我认为理解这一点的关键是要认识到,在执行 for/range 语句主体时,没有当前迭代。有一组已见过的值,和一组尚未见过的值。在执行主体时,已看到的键/值对之一(最新的对)被分配给范围语句的变量。该键/值对没有什么特别的,它只是迭代过程中已经看到的键/值对之一。
他回答的问题是关于在操作期间修改地图元素range,这就是他提到“当前迭代”的原因。但这也与此相关:您可以在范围内删除键,这仅意味着您稍后不会在范围内看到它们(如果您已经看到它们,那也没关系)。
| 归档时间: |
|
| 查看次数: |
32874 次 |
| 最近记录: |