为什么不能按插入顺序迭代映射?

我有一个导航栏作为地图:

var navbar = map[string]navbarTab{ } 

其中navbarTab具有各种属性,子项等。 当我尝试渲染导航栏(与for tabKey := range navbar ),它出现在一个随机的顺序。 我知道range随机sorting时运行,但似乎没有办法得到一个有序列表的键或迭代在插入顺序。

这似乎是荒谬的,这是不可能的。

游乐场链接在这里: http : //play.golang.org/p/nSL1zhadg5虽然它似乎不performance出相同的行为。

如何在不破坏插入顺序的情况下遍历这个映射?

Go地图不保留广告订单; 你将不得不自己实现这个行为。

例:

 type NavigationMap struct { m map[string]navbarTab keys []string } func NewNavigationMap() *NavigationMap { ... } func (n *NavigationMap) Set(k string, v navbarTab) { nm[k] = v n.keys = append(n.keys, k) } 

地图数据结构的一般概念是它是键值对的集合。 没有提到“有序”“sorting”

维基百科定义:

在计算机科学中, 关联数组映射符号表字典是由(key, value)对的集合组成的抽象数据types,使得每个可能的键只出现在集合中一次。

地图是计算机科学中最有用的数据结构之一,所以Go提供它作为一个内置的types。 但是,语言规范只规定了一个普通的地图( 地图types ):

映射是一种types的无序元素集合,称为元素types,由另一种types的唯一键集索引,称为键types。 未初始化映射的值nil

请注意,语言规范不仅排除了“有序”“sorting”的词,而且明确地指出了相反的: “无序的” 。 但为什么? 因为这为运行时提供了更大的自由度来实现地图types。 语言规范允许使用任何地图实现,如哈希映射, 树形图等。请注意,Go的当前(和以前)版本使用哈希映射实现,但您不需要知道如何使用它。

Go map这个博客post是关于这个问题必读的。

在Go 1之前,当映射未被更改时,运行时会多次迭代其键/条目时以相同的顺序返回键。 请注意,如果地图被修改,则此顺序可能已更改,因为实施可能需要执行重新散列以容纳更多条目。 人们开始依赖相同的迭代次序(当map没有被改变时),所以从Go 1开始,运行时随机地图迭代次序是为了引起开发者注意到订单没有被定义,不能被依赖。

那该怎么办?

如果您需要通过插入顺序或由键types或任意顺序定义的自然顺序来sorting数据集(无论是键值对还是其他任何值),则map不是正确的select。 如果你需要预定义的顺序,切片(和数组)是你的朋友。 如果您需要通过预定义键来查找元素,则可以从切片中另外创build一个映射,以便通过快速查找元素。

要么你先build立map ,然后按照适当的顺序build立一个切片,或先切片,然后再从这个切片build立一个map完全取决于你。

前面提到的Go图在行动博客文章中有一个专门用于迭代次序的部分:

当使用范围循环迭代映射时,迭代次序没有被指定,并且不能保证从一次迭代到下一次迭代是相同的。 自Go 1开始,运行时随机化映射迭代顺序,因为程序员依赖于前一个实现的稳定迭代顺序。 如果您需要稳定的迭代顺序,则必须维护一个单独的数据结构来指定该顺序。 这个例子使用一个单独sorting的键片段按键顺序打印一个map[int]string

 import "sort" var m map[int]string var keys []int for k := range m { keys = append(keys, k) } sort.Ints(keys) for _, k := range keys { fmt.Println("Key:", k, "Value:", m[k]) } 

PS:

…虽然似乎不performance出同样的行为。

看起来你在Go游乐场看到“相同的迭代次序”,因为Go Playground上的应用程序/代码的输出被caching 。 一旦一个新的,唯一的代码被执行,它的输出被保存为新的。 一旦执行相同的代码,就会显示保存的输出,而不会再次运行代码。 所以基本上和你所看到的迭代顺序不一样,没有再次执行任何代码就是完全相同的输出。