来自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版本1.8)最多打印到8
,在游乐场(仍然是版本1.8),它打印到3
!
我不太关心游乐场的结果,因为它不是vanilla但是我想知道为什么在我的本地它永远不会打印超过8
?即使我尝试在每次迭代中添加更多项目,以便有可能超过8
,但没有区别。
编辑:基于@Schwern的回答我自己的解释
当使用make
函数创建地图且没有任何大小参数时,仅分配1个存储桶,并且每个存储桶的大小为8个元素,因此当范围开始时,它会看到地图只有1个桶,它将最多迭代8次。如果我使用大于7的大小参数,如make(map[int]int, 8)
,则会创建两个存储桶,并且我可能会在添加的项目上获得超过8次迭代。
答案 0 :(得分:1)
这是大多数哈希表设计中固有的问题。这是一个简单的解释,挥舞着许多不必要的细节。
在引擎盖下,哈希表是一个数组。使用hash function将每个键映射到数组中的元素。例如,“foo”可能映射到元素8,“bar”可能映射到元素4,依此类推。有些元素是空的。
for k,v := range hash
以它们恰好出现的顺序遍历此数组。排序是不可预测的,以避免collision attack。
添加到哈希时,它会添加到基础数组中。它甚至可能需要分配一个新的更大的数组。这个新密钥将落在哈希数组中,这是不可预测的。
因此,如果在迭代哈希时添加更多对,则不会看到在当前索引之前放入数组的任何对;迭代已经过了那一点。任何在之后被放置的东西可能会被看到;迭代还没有到达那一点,但是数组可能会重新分配,并且对可能会重新进行。
但我想知道为什么在我的本地它永远不会打印超过8
因为底层数组可能长度为8. Go可能以2的幂分配底层数组,并且可能从8开始。range hash
可能从检查底层数组的长度开始,不会再进一步,即使它已经成长。
长话短说:在迭代过程中不要为哈希添加键。