为什么在迭代过程中将项目添加到地图会产生不一致的结果?

时间:2017-06-09 02:43:15

标签: go

来自Go Spec

  

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

所以我对该声明的期望是以下代码至少应该打印数字1,并且每次运行程序时,要打印的数字是多少是不可预测的并且不同:

package main

import (
    "fmt"
)

func main() {

    test := make(map[int]int)

    test[1] = 1
    j := 2
    for i, v := range test {
        fmt.Println(i, v)
        test[j] = j
        j++
    }

    return

}

Go playground link

在我自己的笔记本电脑上(Go版本1.8)最多打印到8,在游乐场(仍然是版本1.8),它打印到3! 我不太关心游乐场的结果,因为它不是vanilla但是我想知道为什么在我的本地它永远不会打印超过8?即使我尝试在每次迭代中添加更多项目,以便有可能超过8,但没有区别。

编辑:基于@Schwern的回答我自己的解释

当使用make函数创建地图且没有任何大小参数时,仅分配1个存储桶,并且每个存储桶的大小为8个元素,因此当范围开始时,它会看到地图只有1个桶,它将最多迭代8次。如果我使用大于7的大小参数,如make(map[int]int, 8),则会创建两个存储桶,并且我可能会在添加的项目上获得超过8次迭代。

1 个答案:

答案 0 :(得分:1)

这是大多数哈希表设计中固有的问题。这是一个简单的解释,挥舞着许多不必要的细节。

在引擎盖下,哈希表是一个数组。使用hash function将每个键映射到数组中的元素。例如,“foo”可能映射到元素8,“bar”可能映射到元素4,依此类推。有些元素是空的。

for k,v := range hash以它们恰好出现的顺序遍历此数组。排序是不可预测的,以避免collision attack

添加到哈希时,它会添加到基础数组中。它甚至可能需要分配一个新的更大的数组。这个新密钥将落在哈希数组中,这是不可预测的。

因此,如果在迭代哈希时添加更多对,则不会看到在当前索引之前放入数组的任何对;迭代已经过了那一点。任何在之后被放置的东西可能会被看到;迭代还没有到达那一点,但是数组可能会重新分配,并且对可能会重新进行。

  

但我想知道为什么在我的本地它永远不会打印超过8

因为底层数组可能长度为8. Go可能以2的幂分配底层数组,并且可能从8开始。range hash可能从检查底层数组的长度开始,不会再进一步,即使它已经成长。

长话短说:在迭代过程中不要为哈希添加键。