【问题标题】:Getting a slice of keys from a map从地图中获取一部分键
【发布时间】:2014-02-17 05:37:33
【问题描述】:

有没有更简单/更好的方法从 Go 中的映射中获取一个键片段?

目前我正在遍历地图并将键复制到切片:

i := 0
keys := make([]int, len(mymap))
for k := range mymap {
    keys[i] = k
    i++
}

【问题讨论】:

    标签: go


    【解决方案1】:

    一个更好的方法是使用append

    keys = []int{}
    for k := range mymap {
        keys = append(keys, k)
    }
    

    除此之外,你很不走运——Go 不是一种表达能力很强的语言。

    【讨论】:

    • 虽然它比原来的效率低 - append 将进行多次分配以增加底层数组,并且每次调用都必须更新切片长度。说keys = make([]int, 0, len(mymap)) 将摆脱分配,但我预计它仍然会更慢。
    • 这个答案比使用 len(mymap) 更安全,如果其他人在复制过程中更改了地图。
    • @AtilaRomero 是真的吗?我会假设在这种情况下,数据可能完全损坏,或者这是在 golang 的某个地方指定的?
    • @Petr 我认为应该恐慌。不应该有两个例程在同一张地图上工作。这就是 sync.Map 的用途,或者使用带有 Mutexes 的地图
    【解决方案2】:

    例如,

    package main
    
    func main() {
        mymap := make(map[int]string)
        keys := make([]int, 0, len(mymap))
        for k := range mymap {
            keys = append(keys, k)
        }
    }
    

    为了在 Go 中高效,重要的是尽量减少内存分配。

    【讨论】:

    • 最好设置实际大小而不是容量,并且完全避免追加。详情见我的回答。
    • 请注意,如果 mymap 不是局部变量(因此会增长/缩小),这是唯一合适的解决方案 - 它确保如果 mymap 的大小在keysfor 循环的初始化,不会有任何越界问题。
    • map 在并发访问下是不安全的,如果另一个 goroutine 可能会更改 map,这两种解决方案都不可接受。
    • @VinayPai 可以从多个 goroutine 中读取映射但不能写入
    • @darethas 这是一个常见的误解。从 1.6 开始,比赛检测器将标记这种用法。来自发行说明:“与往常一样,如果一个 goroutine 正在写入 map,则不应有其他 goroutine 并发读取或写入 map。如果运行时检测到这种情况,它会打印诊断信息并使程序崩溃。” golang.org/doc/go1.6#runtime
    【解决方案3】:

    这是一个老问题,但这是我的两分钱。 PeterSO 的回答稍微简洁一些,但效率稍低。你已经知道它会有多大,所以你甚至不需要使用 append:

    keys := make([]int, len(mymap))
    
    i := 0
    for k := range mymap {
        keys[i] = k
        i++
    }
    

    在大多数情况下,它可能不会有太大的不同,但它并没有更多的工作,并且在我的测试中(使用带有 1,000,000 个随机 int64 键的映射,然后每个键生成十次键数组方法),直接分配数组成员比使用 append 快 20%。

    虽然设置容量消除了重新分配,但追加仍然需要做额外的工作来检查每次追加是否已达到容量。

    【讨论】:

    • 这看起来与 OP 的代码完全相同。我同意这是更好的方法,但我很好奇我是否错过了这个答案的代码和 OP 的代码之间的区别。
    • 好点,我以某种方式查看了其他答案并错过了我的答案与 OP 完全相同。哦,好吧,至少我们现在大概知道不必要地使用 append 的惩罚是什么了 :)
    • 你为什么不使用具有范围的索引,for i, k := range mymap{。这样你就不需要 i++了?
    • 也许我在这里遗漏了一些东西,但如果你做了i, k := range mymap,那么i 将是键,k 将是对应于映射中这些键的值。这实际上并不能帮助您填充一个键。
    • @Alaska 如果您担心分配一个临时计数器变量的成本,但认为函数调用会占用更少的内存,您应该了解调用函数时实际发生的情况。提示:这不是免费做事的魔法咒语。如果您认为当前接受的答案在并发访问下是安全的,您还需要回到基础:blog.golang.org/go-maps-in-action#TOC_6
    【解决方案4】:

    您还可以通过“reflect”包中的结构Value 的方法MapKeys 获取类型为[]Value 的键数组:

    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    func main() {
        abc := map[string]int{
            "a": 1,
            "b": 2,
            "c": 3,
        }
    
        keys := reflect.ValueOf(abc).MapKeys()
    
        fmt.Println(keys) // [a b c]
    }
    

    【讨论】:

    • 如果存在并发地图访问的机会,我认为这是一个好方法:如果地图在循环期间增长,这不会恐慌。关于性能,我不太确定,但我怀疑它的性能优于附加解决方案。
    • @AtilaRomero 不确定这个解决方案有什么优势,但是当出于任何目的使用反射时,这更有用,因为它允许将键作为直接键入的值。
    • 有没有办法把它转换成[]string
    • @AtilaRomero 我刚刚测试了它,当列表增长时它仍然会恐慌。
    • 这是我一直在寻找的答案。我正在尝试合并两个不同的“实体”结构,它们都包含一个 map[string][]*Foo。所以,对于第一个结构,我需要识别键,所以我可以通过键在第二个结构中查找它,而不是遍历每个映射值来查看它们是否是我想要合并的键。谢谢!
    【解决方案5】:

    我对其他回复中描述的三种方法做了一个粗略的基准测试。

    显然在拉键之前预分配切片比appending 快,但令人惊讶的是,reflect.ValueOf(m).MapKeys() 方法比后者慢得多:

    ❯ go run scratch.go
    populating
    filling 100000000 slots
    done in 56.630774791s
    running prealloc
    took: 9.989049786s
    running append
    took: 18.948676741s
    running reflect
    took: 25.50070649s
    

    这是代码:https://play.golang.org/p/Z8O6a2jyfTH (在操场上运行它会中止声称它需要太长时间,所以,好吧,在本地运行它。)

    【讨论】:

    • 在您的keysAppend 函数中,您可以使用make([]uint64, 0, len(m)) 设置keys 数组的容量,这对我来说极大地改变了该函数的性能。
    • @keithbhunter 我同意,差别很小。
    • @keithbhunter 调用 make([]int, len(m)) 和 make([]int, 0, len(m) 实际上是同一件事:在内存中预分配数组,这将完全失败测试的目的。
    【解决方案6】:

    访问https://play.golang.org/p/dx6PTtuBXQW

    package main
    
    import (
        "fmt"
        "sort"
    )
    
    func main() {
        mapEg := map[string]string{"c":"a","a":"c","b":"b"}
        keys := make([]string, 0, len(mapEg))
        for k := range mapEg {
            keys = append(keys, k)
        }
        sort.Strings(keys)
        fmt.Println(keys)
    }
    

    【讨论】:

      【解决方案7】:

      最终,Go 很快就会有泛型。如果这个提案(https://github.com/golang/go/issues/47649)进入了泛型实现的第一版,我们可以通过maps.Keys获取任意map的key:

      // Keys returns the keys of the map m.
      // The keys will be an indeterminate order.
      func Keys[M constraints.Map[K, V], K comparable, V any](m M) []K
      

      示例使用很简单

          intMap := map[int]int{1: 1, 2: 2}
          intKeys := maps.Keys(intMap)
          // intKeys is []int
          fmt.Println(intKeys)
      
          strMap := map[string]int{"alpha": 1, "bravo": 2}
          strKeys := maps.Keys(strMap)
          // strKeys is []string
          fmt.Println(strKeys)
      

      maps 包位于golang.org/x/exp/maps。这是实验性的,不属于 Go 兼容性保证。他们的目标是将其移入 Go 1.19 中的 std lib

      蚂蚁方式,它适用于 gotip 游乐场 https://gotipplay.golang.org/p/fkm9PrJYTly

      【讨论】:

      • 对泛型感到非常兴奋,等不及了
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-29
      • 1970-01-01
      • 1970-01-01
      • 2011-07-08
      • 1970-01-01
      • 2017-08-16
      • 1970-01-01
      相关资源
      最近更新 更多