从地图上获取一部分钥匙


230

在Go中,有没有更简单/更巧妙的方法从地图中获取键的片段?

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

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

19
正确的答案是否定的,没有更简单/更精细的方法。
dldnh

Answers:


202

例如,

package main

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

为了在Go中高效运行,最小化内存分配非常重要。


29
最好设置实际大小而不是容量,并避免完全附加。有关详细信息,请参见我的答案。
Vinay Pai 2015年

3
请注意,如果mymap不是局部变量(因此可能会增大/缩小),那么这是唯一合适的解决方案-它可以确保,如果mymap初始化keysfor循环之间的变化幅度不大,边界问题。
Melllvar '17

12
在并发访问下使用映射是不安全的,如果另一个goroutine可能会更改映射,则两种解决方案都不可接受。
Vinay Pai

@VinayPai可以从多个goroutines的地图中读取但不能写入
darethas

@darethas这是一个常见的误解。从1.6开始,竞赛检测器将标记此用法。从发行说明中:“与往常一样,如果一个goroutine正在写入映射,则其他goroutine不应同时读取或写入映射。如果运行时检测到此情况,它将打印诊断并导致程序崩溃。” golang.org/doc/go1.6#runtime
Vinay Pai

375

这是一个老问题,但这是我的两分钱。PeterSO的答案更为简洁,但效率略低。您已经知道它的大小,因此您甚至不需要使用append:

keys := make([]int, len(mymap))

i := 0
for k := range mymap {
    keys[i] = k
    i++
}

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

尽管设置容量可以消除重新分配,但是append仍必须做额外的工作来检查每个追加的容量是否已达到。


46
这看起来与OP的代码完全相同。我同意这是更好的方法,但是我很好奇我是否错过了此答案的代码和OP的代码之间的区别。
艾玛莉·威尔逊

4
好点,我以某种方式查看了其他答案,却错过了我的答案与OP完全相同的答案。哦,嗯,​​至少我们现在知道大约不必要地使用了追加是什么惩罚:)
Vinay Pai

5
为什么不使用范围为的索引for i, k := range mymap{。这样,您就不需要i ++了吗?
mvndaai 2016年

30
也许我在这里丢失了一些东西,但是如果您这样做了i, k := range mymap,那么i它将是键,并且k将是对应于地图中那些键的值。这实际上并不会帮助您填充一部分键。
Vinay Pai

4
@Alaska如果您担心分配一个临时计数器变量的开销,但是认为函数调用会占用更少的内存,则应该对自己在调用函数时实际发生的情况进行了解。提示:免费做事不是神奇的咒语。如果您认为当前接受的答案在并发访问下是安全的,则还需要回到基础知识:blog.golang.org/go-maps-in-action#TOC_6
Vinay Pai

79

您还可以从“ reflect”包中[]Value通过MapKeysstruct 方法获取类型为键的数组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]
}

1
我认为如果有并发的地图访问机会,这是一个好方法:如果地图在循环期间增长,这不会惊慌。关于性能,我不确定,但是我怀疑它的性能优于附加解决方案。
阿蒂拉·罗梅罗

@AtilaRomero不确定此解决方案是否具有任何优点,但是当出于任何目的使用反射时,此方法将更有用,因为它允许将键作为直接键入Value的键。
丹尼斯·克雷希欣

10
有没有办法将其转换为[]string
多伦·比哈

14

一个更好的方法是使用append

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

除此之外,您还很不走运-Go不是一种非常有表现力的语言。


10
但是,它的效率比原始方法低-append将进行多次分配以增长基础数组,并且每次调用都必须更新切片长度。说keys = make([]int, 0, len(mymap))可以摆脱分配,但我希望它会更慢。
Nick Craig-Wood

1
如果有人在复制过程中更改了地图,则此答案比使用len(mymap)更安全。
阿蒂拉·罗梅罗

7

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

显然,在拉动键之前预先分配片比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 (在操场上运行它会中止声称它花费的时间太长,所以,可以在本地运行它。)


2
在您的keysAppend函数中,可以使用设置keys数组的容量make([]uint64, 0, len(m)),这对我来说极大地改变了该函数的性能。
keithbhunter

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.