Go语言 如何按插入顺序迭代Map?

hmae6n7t  于 2023-04-27  发布在  Go
关注(0)|答案(2)|浏览(156)

我有一个导航栏作为Map:

var navbar = map[string]navbarTab{
}

其中navbarTab有各种属性,子项等等。当我尝试渲染导航栏(使用for tabKey := range navbar)时,它以随机顺序显示。我知道range在运行时会随机排序,但似乎没有办法获得有序的键列表或以插入顺序迭代。
Playground链接在这里:http://play.golang.org/p/nSL1zhadg5,尽管它似乎没有表现出相同的行为。
如何在不破坏插入顺序的情况下迭代这个Map?

eh57zj3b

eh57zj3b1#

map数据结构的 * 一般 * 概念是它是一个键值对的集合。"有序""排序" 没有提到。
Wikipedia definition:
在计算机科学中,关联数组Map符号表字典是一种抽象数据类型,由(key, value)对的集合组成,这样每个可能的键在集合中只出现一次。

  • map * 是计算机科学中最有用的数据结构之一,因此Go语言将其作为内置类型提供。然而,语言规范只指定了一个 * general * map(Map类型):

一个map是一组无序的元素,它们的类型称为元素类型,由另一种类型的唯一键索引,称为键类型。未初始化的map的值是nil
请注意,语言规范不仅省略了 "ordered""sorted" 这些词,它还明确指出了相反的情况:* "unordered"**。但是为什么呢?因为这给了运行时 * 更大的自由度 * 来实现map类型。语言规范允许使用任何map实现,比如 * hash * map, tree * map等。注意,当前(和以前)版本的Go使用hash map实现,但是你不需要知道它就可以使用它。
博客文章Go maps in action是关于这个问题的必读文章。
在Go 1之前,当map没有改变时,当你多次迭代它的键/条目时,运行时会以相同的顺序返回键。注意,如果map被修改,这个顺序可能会改变,因为实现可能需要重新散列以容纳更多的条目。人们开始依赖相同的迭代顺序(当map没有改变时),所以从Go 1开始,运行时随机化map迭代顺序故意以引起开发人员的注意,顺序没有定义并且不能依赖。
那怎么办?
如果你需要一个 * sorted * 数据集(无论是键值对的集合还是其他任何东西),无论是按插入顺序,还是按键类型定义的自然顺序,还是按任意顺序,map都不是正确的选择。(和数组)是你的朋友。如果你需要能够通过预定义的键查找元素,你可以 * 另外 * 从切片中构建一个Map,以允许通过 * 键 * 快速查找元素。
您可以先构建map,然后按正确的顺序构建一个切片,或者先构建切片,然后再构建map,这完全取决于您。
前面提到的 * Go maps in action * 博客中有一节专门讨论迭代顺序
当使用range循环迭代map时,迭代顺序没有指定,并且不能保证从一个迭代到下一个迭代都是相同的。由于Go 1运行时随机化map迭代顺序,因为程序员依赖于前一个实现的稳定的迭代顺序。如果你需要一个稳定的迭代顺序,你必须维护一个单独的数据结构来指定那个顺序。这个例子使用一个单独的排序的key切片来按key顺序打印一个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])
}
    • 附:**

虽然它似乎没有表现出同样的行为。
看起来你在Go Playground上看到的是“相同的迭代顺序”,因为Go Playground上的应用程序/代码的输出都被缓存了。一旦执行了一个新的、唯一的代码,它的输出就会被保存为新的。一旦执行了相同的代码,保存的输出就会显示出来,而不会再次运行代码。所以基本上它和你看到的迭代顺序不一样,这是完全相同的输出,而无需再次执行任何代码。

    • 附文2**

虽然使用for range的迭代顺序是“随机的”,但在标准库中有一些明显的例外,即encoding/jsontext/templatehtml/templatefmt包。有关更多详细信息,请参见In Golang,why are iterations over maps random?

jyztefdp

jyztefdp2#

GoMap不维护插入顺序;您必须自己实现此行为。
示例:

type NavigationMap struct {
    m map[string]navbarTab
    keys []string
}

func NewNavigationMap() *NavigationMap { ... }

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

此示例并不完整,并且没有涵盖所有用例(例如,更新重复键的插入顺序)。
如果您的用例包括多次重新插入相同的键(如果键 k 已经在Map中,则这将不会更新键 k 的插入顺序):

func (n *NavigationMap) Set(k string, v navbarTab) {
    _, present := n.m[k]
    n.m[k] = v
    if !present {
        n.keys = append(n.keys, k)
    }
}

选择最简单的东西来满足你的需求。

相关问题