参考大佬博客,下面这个结论我对后半截不是很理解。
如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。
Q1:单携程内,map可以边遍历边删除吗?
A:可以。不会panic。 因为写标记检查一直都能通过: 删除时标记1,删除后复位0,然后重复这个过程,所以可以一直正常删除。
Q2:一边遍历一边删除,遍历结果是否包含被删除的元素?
A: 分两种情况讨论
情况1:只删除当前元素
下面2种形式都是只删除当前元素,都可以遍历到全部5个元素(包括会被删除的元素),即: 遍历结果集中肯定包含了全部被删除的key
m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
for k := range m {
fmt.Println("Current key:", k)
delete(m, k) // 删除当前遍历的元素
}
for k := range m {
fmt.Println("Current key:", k)
if k == "b" || k == "d" {
delete(m, k) // 删除当前遍历的元素
}
}
情况2:删除非当前遍历的元素
比如遍历顺序是a->b,如果遍历a时,把b删除了(会把b元素对应的tophash给标记为empty),之后遍历b元素发现删除标记,则会跳过该元素。
即:删除key的时间:是在遍历到 key 所在的 bucket 时刻前删除了key,结果集不会包含删除key
相反的场景,比如遍历删除是b->a,那结果会包含b,因为b先遍历到,然后在遍历a时才删除。
即:删除key的时间:是在遍历到 key 所在的 bucket 时刻后,依然会遍历到key(b),结果集会包含删除key
结论: 因为key遍历顺序随机,所以这种前后关系也会变化,所以有时结果集包含被删除元素,有时不包含被删除元素。即:有可能遍历结果集中包含了删除的 key,也有可能不包含, 这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。
m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
for k := range m {
fmt.Println("Current key:", k)
if k == "a" || k == "c" || k == "e" {
// 删除非当前遍历的元素
if k == "a" {
delete(m, "b")
}
if k == "c" {
delete(m, "d")
}
}
}
fmt.Println("Final map:", m)
- 遍历过程详细步骤
假设遍历顺序是 "a" -> "b" -> "c" -> "d" -> "e",并且我们在遍历过程中删除 "b" 和 "d",但只在访问其他键时进行删除操作。
-
访问
"a":- 输出:
"Current key: a" - 当前
bucket0中的元素:["a" -> 1, "b" -> 2] - 删除
"b":delete(m, "b") - 此时
bucket0中的元素变为:["a" -> 1, emptyOne] - 迭代器继续前进到下一个非空元素
"c"
- 输出:
-
访问
"b":- 由于
"b"已经被删除,迭代器跳过该位置。 - 输出:无(因为
"b"已被删除)
- 由于
-
访问
"c":- 输出:
"Current key: c" - 当前
bucket1中的元素:["c" -> 3, "d" -> 4] - 删除
"d":delete(m, "d") - 此时
bucket1中的元素变为:["c" -> 3, emptyOne] - 迭代器继续前进到下一个非空元素
"e"
- 输出:
-
访问
"d":- 由于
"d"已经被删除,迭代器跳过该位置。 - 输出:无(因为
"d"已被删除)
- 由于
-
访问
"e":- 输出:
"Current key: e" - 当前
bucket2中的元素:["e" -> 5] - 遍历结束
- 输出:
最终输出结果为:
Current key: a
Current key: c
Current key: e
Final map: map[a:1 c:3 e:5]
Q:遍历删除的最佳实践
先搜集删除key后再删除,不要一边遍历,一边删除
toDelete := []string{}
for k := range m {
if someCondition(k) {
toDelete = append(toDelete, k)
}
}
for _, k := range toDelete {
delete(m, k)
}