Getting a slice of keys from a map
在Go中,有没有更简单/更好的方法从地图中获取一个关键点?
目前我正在迭代映射并将键复制到一个切片:
1 2 3 4 5 6
| i := 0
keys := make([]int, len(mymap))
for k := range mymap {
keys[i] = k
i++
} |
这是个老问题,但这是我的两分钱。彼得索的回答稍微简洁一些,但效率稍低一些。您已经知道它将有多大,所以您甚至不需要使用append:
1 2 3 4 5 6 7
| keys := make([]int, len(mymap))
i := 0
for k := range mymap {
keys[i] = k
i++
} |
在大多数情况下,它可能不会有太大的区别,但它不会有太多的工作,在我的测试中(使用一个包含1000000个随机int64键的映射,然后用每个方法生成键数组10次),直接分配数组成员比使用append快20%。
尽管设置容量可以消除重新分配,但是append仍然需要做额外的工作来检查您是否已经达到了每个append的容量。
- 这看起来和操作代码完全一样。我同意这是更好的方法,但是我很好奇我是否错过了这个答案的代码和op的代码之间的区别。
- 好的,我不知怎么看了其他的答案,但没注意到我的答案和OP是完全一样的。哦,好吧,至少我们现在知道了不必要地使用append的惩罚是什么:)
- 为什么不使用范围为for i, k := range mymap{的索引呢?这样你就不需要I++?
- 也许我这里遗漏了一些东西,但是如果你做了i, k := range mymap,那么i将是键,k将是对应于地图中这些键的值。这实际上不会帮助您填充一个键片。
- 请注意,如果mymap经常发生更改,则不建议使用此解决方案-请参阅我对已接受答案的评论。
- 映射对于并发访问是不安全的,您需要一个互斥体或其他一些机制来防止在迭代过程中有多个goroutine对其进行修改。
- 我不同意@dustyw的说法,追加版本读起来更好,更简洁,不需要计数器变量。
- 任何能浮在你船上的东西。我个人更愿意编写两行长但效率更高的代码。
- 我同意维奈在效率方面的努力。在这种情况下,可以使用此工具在代码中发现预分配的机会:github.com/alexkohler/pre alloc
- 并发性在这里可能会带来一些问题:如果在复制过程中映射大小增大,则最终可能会尝试插入超过数组限制的ITEN。在这种情况下,附加解决方案或反射解决方案更好。
- 映射对于并发访问不安全。如果可能的话,你需要重新考虑整个解决方案以确保安全。
- @阿拉斯加如果你关心分配一个临时计数器变量的成本,但是认为函数调用会占用更少的内存,你应该告诉自己当函数被调用时实际会发生什么。提示:这不是一种免费的魔法咒语。如果您认为当前接受的答案在并发访问下是安全的,那么您还需要返回到基础知识:blog.golang.org/go maps in action_toc_6。
例如,
1 2 3 4 5 6 7 8 9
| 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的大小在keys和for循环的初始化之间发生变化,则不会出现任何越界问题。
- 在并发访问下,映射是不安全的,如果另一个goroutine可能更改映射,则这两种解决方案都是不可接受的。
- @Vinaypai可以从多个goroutine中读取地图,但不能写入
- @这是一个常见的误解。竞速检测器将从1.6开始标记此用法。从发行说明中可以看出:"和往常一样,如果一个goroutine正在写入一个映射,其他goroutine不应该同时读取或写入该映射。如果运行时检测到这种情况,它会打印一个诊断并使程序崩溃。
- @Vinay Pai说得对,我本不该说"好"但"安全"。现在,这是否会导致程序的正确行为是另一个故事,您是对的。
您还可以从包"reflect"中通过方法MapKeys从结构Value中获取类型为[]Value的键数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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不确定此解决方案是否有任何优势,但当出于任何目的使用反射时,这更有用,因为它允许将键作为直接键入的值。
更好的方法是使用append:
1 2 3 4
| keys = []int{}
for k := range mymap {
keys = append(keys, k)
} |
除此之外,你走运了,go不是一种很有表现力的语言。
- 它的效率低于原始的尽管-append将执行多个分配来增加底层数组,并且每次调用都必须更新切片长度。说keys = make([]int, 0, len(mymap))将取消分配,但我预计仍将放缓。
- 这个答案比使用len(mymap)更安全,如果有人在复制时更改了地图。
我对其他回答中描述的三种方法做了粗略的基准测试。
显然,在拔出键之前预先分配切片比appending快,但令人惊讶的是,reflect.ValueOf(m).MapKeys()方法比后者慢得多:
1 2 3 4 5 6 7 8 9 10
| ? 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数组的容量,这彻底改变了该函数对我的性能。
- 我总是忘了这一点。