在 Go 中迭代映射时修改映射

问题描述 投票:0回答:4

鉴于以下代码,我预计会出现无限循环,但循环在某个点停止。

m := make(map[int]string, 4)
m[0] = "Foo"
    
for k, v := range m {
    m[k+1] = v
}

我无法弄清楚幕后发生了什么,因为不同的执行会返回不同的输出。例如,这些是不同执行的一些输出:

map[0:Foo 1:Foo 2:Foo 3:Foo 4:Foo 5:Foo 6:Foo 7:Foo]
map[0:Foo 1:Foo]
map[0:Foo 1:Foo 2:Foo]

range
如何工作以便在某个点退出循环以及退出条件是什么?

dictionary go
4个回答
14
投票

规范:对于带有范围子句的语句表示行为是不可预测的:

映射的迭代顺序未指定,并且不保证从一次迭代到下一次迭代的顺序相同。如果在迭代过程中删除了尚未到达的映射条目,则不会产生相应的迭代值。 如果在迭代期间创建了映射条目,则该条目可能会在迭代期间生成,也可能会被跳过。对于创建的每个条目以及从一次迭代到下一次迭代,选择可能会有所不同。 如果地图是

nil
,则迭代次数为 0。

向您正在浏览的地图添加元素,这些条目可能会或可能不会被循环访问,您不应该对此做出任何假设。


3
投票

基于语言规范:

如果在迭代期间创建了映射条目,则该条目可能会在迭代期间生成,也可能会被跳过。

因此,如果跳过新元素,for循环最终会结束。


3
投票

其他答案已经解释了您在代码片段中观察到的行为。

因为您的标题相当通用,但您的代码片段仅涵盖了在迭代地图时添加地图条目,这里有一个补充示例,应该让您相信在迭代地图时“交叉删除”地图条目是一个坏主意( 游乐场):

package main

import "fmt"

func main() {
    m := map[string]int{"foo": 0, "bar": 1, "baz": 2}
    for k := range m {
        if k == "foo" {
            delete(m, "bar")
        }
        if k == "bar" {
            delete(m, "foo")
        }
    }
    fmt.Println(m)
}

规格说

映射的迭代顺序未指定,并且不保证从一次迭代到下一次迭代的顺序相同。 如果迭代过程中删除了尚未到达的映射条目,则不会产生相应的迭代值。

结果,程序输出

map[bar:1 baz:2]
map[baz:2 foo:0]
,但无法区分是哪一个。


0
投票

心智模型。

Go 映射是哈希映射。简单来说,这意味着它们是底层的数组。键通过哈希转换为索引(因此称为 hash 映射)。至关重要的是,每个映射都有一个唯一的盐,哈希函数使用该盐来确保不同的映射在底层数组中以不同的顺序存储元素。

迭代映射就是简单地遍历数组。当您在迭代期间编写映射时,会将新元素插入到数组中。这可能发生在当前元素的左侧(在这种情况下,迭代不会访问该元素),也可能发生在右侧(在这种情况下,它将被访问)。

由于盐的独特性,插入的元素有时会向左,有时向右,因此结果是不确定的。

© www.soinside.com 2019 - 2024. All rights reserved.