Go语言map实践
Posted 容器时代
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言map实践相关的知识,希望对你有一定的参考价值。
声明和初始化
Go语言中的映射类型看起来像这样:
map [KeyType] ValueType
KeyType可以是任意可比较的类型 、ValueType可以是任意类型,甚至是一个其他的映射。如下m为映射类型的变量,Key是string类型的,Value是int类型:
var m map[string]int
映射是引用类型,类似于指针或切片(slice),所以上面m的是值是nil,并没有被初始化;读取nil 映射同读取空映射一样,但对nil映射进行写操作时会导致运行时错误,所以不要像上面那样初始化map,需要使用make函数:
m = make(map[string]int)
make函数会分配和初始化一个哈希映射数据结构,并返回一个指向该哈希映射的映射变量。相关数据结构细节由运行时实现,并且不因语言本身所规定。本文中我们将关注映射的使用,而不是他们的实现。
映射的使用
Go语言提供常见的映射使用语法,下面的声明是将键“route”赋值为66
m[“route”] = 66
这个声明是将值存储在键“route”下,返回并分配给新的变量i:
i := m[“route”]
如果返回的键不存在,我们得到值类型对应的零值 ;这个例子中值的类型是int类型,所以零值是0:
j := m[“root”]
// j == 0
len函数可以返回映射中条目的数量:
n := len(m)
delete函数可以删除映射中的一个条目:
delete(m, “route”)
delete函数不会返回任何值,如果特定的键不存在,那么将不会执行任何操作。
给一个已存在的键进行二值分配测试:
i, ok := m[“route”]
在这种情况下,第一个值(i)用于存储键“route”对应的值。如果这个键不存在,i的值是该类型的零值(0)。第二个值(ok)是布尔类型的值,如果被测试的键在映射中存在返回true,否则返回false。
如果不返回键的值,则第一个值使用下划线表示:
_, ok := m[“route”]
使用range关键字遍历映射中的内容:
for key, value := range m {
fmt.println(“Key:”, key, “Value:”, value)
}
使用映射字面量进行初始化::
commits := map [string] int {
“rsc” : 3711,
“r” : 2138,
“gri” : 1908,
“adg” : 912,
}
这种方法同样可以初始化一个空的映射,此时等价于make函数的功能相同:
m = map[string] int { }
零值的使用
当键不存在时,映射返回零值,这样处理是相当方便的。
像下面这个例子,一个值为布尔型的映射可以被用作集合来使用(布尔类型的零值为false)。这个例子是遍历Nodes链表并打印出他们的值。使用Node指针类型的映射来检测链表中的循环。
type Node struct {
Next *Node
Value interface{ }
}
var first *Node
visited := make(map[*Node]bool)
for n := first; n != nil; n = n.Next {
if visited[n] {
fmt.Println(“cycle detected”)
break
}
visited[n] = true
fmt.Println(n.Value)
}
这表明,如果检测到n,则visited[n]返回true,如果不存在,则返回false。在映射中没有必要使用2个值的形式去检测n是否存在;对于我们来说零值默认的。
另一个例子,展示了在切片类型的映射中零值的好处。向一个nil切片进行追加会创建一个新的slice,这等同于给切片类型的映射增加一个值;这种情况没有必要检查键是否存在。在接下来的例子中,切片people 被Person类型的值填充。每一个Person都有一个Name和一系列Likes。这个例子创建一个映射,将每个like的值与一组喜欢它的people关联起来。
type Person struct {
Name string
Likes []string
}
var people []*Person
likes := make(map[string][]*Person)
for _, p := range people {
for _, l := range p.Likes {
likes[l] = append(likes[l], p)
}
}
打印那些like cheese 的people列表:
for _, p := range likes["cheese"] {
fmt.Println(p.Name, "likes cheese.")
}
打印like bacon的people的数量:
fmt.Println(len(likes["bacon"]), "people like bacon.”)
需要注意的是,对于空的切片,关键字range和len返回的都是长度0. 最后两个例子即使没有人喜欢 cheese 或者 bacon,程序也会被执行(无论不存在那种可能)
键的类型
在前文提到过映射的键可能是可比较的任意类型。Go语言规范有明确的定义,简单说,可比较的类型包含布尔类型、数值型、字符串、指针、管道、接口类型、结构体、数组这些类型。很明显少了列表、切片、映射和函数,这些类型不能使用==进行比较,不能作为映射的键使用。
很明显字符串、整型以及其他基础类型可以作为映射的键,但可能存在意想不到的结构体键,结构体可以被适用于多维度的键数据。例如,映射类型的maps可以被用于统计对应国家的网页点击率:
hits := make(map[string]map[string]int)
这段代码代表了键为string类型,值为“键为string类型,值为int类型的映射”的映射。外层映射的每个键是网页本身的映射的路径。每个内在映射的键为2位国家代码。下面的代码表示了在澳大利亚,/doc/这个文件页被点击的次数。
n := hits[“/doc/”][“au”]
不幸的是,在添加数据时这种方法显得非常笨重,任何被给定的外部键必须检查是否有对应的内部映射存在,然后才能创建:
func add(m map[string]map[string]int, path, country string) {
mm, ok := m[path]
if !ok {
mm = make(map[string]int)
m[path] = mm
}
mm[country]++
}
add(hits, "/doc/", "au")
另一方面,在使用结构体类型的单一映射时却消除了所有的复杂性:
type Key struct {
Path, Country string
}
hits := make(map[Key]int)
当一个越南人访问主页时,一行代码就可以完成计数的增加:
hits[Key{“/”, “vn”}]++
同样我们直接就能得到有多少瑞士人已经读了这个文档:
n := hits[Key{“/ref/spec”, “ch”}]
并发
映射不是并发安全的:映射并没有定义当同时进行读和写时会发生什么。如果你需要执行同时对映射进行读取和写入操作的goroutines时,这种访问必须使用某种同步机制,比如sync.RWMutex.
这个例子声明了一个匿名结构体类型的counter变量,这个结构体包含了一个映射以及一个被嵌入的sync.RWMutex。
var counter = struct{
sync.RWMutex
m map[string]int
}{m: make(map[string]int)}
从counter中执行带锁的读操作:
counter.RLock()
n := counter.m["some_key"]
counter.RUnlock()
fmt.Println("some_key:", n)
从counter中执行带锁的写操作:
counter.Lock()
counter.m["some_key"]++
counter.Unlock()
迭代顺序
当使用range循环遍历一个映射时,遍历顺序并不是确定的,不能保证下次遍历的结果与这次的结果相同。从Go 1版本开始,对映射遍历顺序进行了随机化处理。
如果你需要一个固定的遍历过程,你必须去维护一个明确且独立的数据结构。下面的例子中使用单独的排序过的切片作为键,并以此为依据中打印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])
}
参考资料
原文链接:https://blog.golang.org/go-maps-in-action
KubeCon 上海
以上是关于Go语言map实践的主要内容,如果未能解决你的问题,请参考以下文章