go map

来源:互联网 发布:网络请求失败怎么解决 编辑:程序博客网 时间:2024/04/29 11:16

哈希表是计算机里面比较重要的一个数据结构, 虽然各个哈希表的实现不同, 但是基本上不同的实现都提供快速的增、删、改、查功能。 go语言用map来提供哈希表的功能。

声明和初始化

go语言的map有如下的形式: map[KeyType]ValueType    

其中, 要求KeyType必须是 可以比较的( 可以使用==和!=号), ValueType可以是任何类型, 当然这个任意类型也包括另外一个map,比如 map[string]map[string]int 是一个合法的map。

如果有如下语句: var m map[string]int ,  那么变量m就是一个key为string类型, value为int类型的map,因为map是引用类型(和go里面的slice一样), 所以m的值是nil,它没有指向任何内部的map数据结构,你可以读这个m,但是写它是会panic掉的,所以你最好不要这样来声明m, 你可以用go自带的make函数: m = make(map[string]int) 来初始化m,这个时候m不是nil了, 你既可以读又可以写这个m了。 当然你也可以用  m = map[string]int{} 来初始化一个空的map, 这个和上面的make函数效果是一样的, 但是我个人还是推荐用make函数来初始化map。

使用map

go为你提供了你熟悉的方式来操作map, 比如 m[“route”] = 66 把key”route”赋了值66。 读取这个key的值并赋给一个新的变量可以用: i := m[“route”] 语句。 如果key不存在, 那么得到的值将是ValueType的 零值, 比如 z := m[“not-exist”] ,因为m的值类型是int型的, 所以z的值会为int类型的零值, 也就是0.

我们可以通过内置函数len来得到map里面元素的个数, 比如len(m)会返回m里面有多少个元素。 如果你想删除map里面的一个特定的key, 你可以用go内置的delete函数, 比如delete(m, “to-be-deleted”) 来删除可以为 “to-be-deleted”的元素。 delete函数没有任何返回值, 如果要删除的key不存在, 那么就不会对这个map做任何修改。 

刚才我们说过, 如果取值的时候, key不存在, 那么将返回值类型的零值, 那么假如有如下语句: a := m[“mykey”] , 通过这个语句, a的值为0, 那么我们怎么知道是mykey不存在, 还是说mykey存在并且对应的value值为0呢? go为我们提供了一种方式来区分这个情况, a, ok := m[“mykey”] ,如果mykey不存在,则ok为false, 如果mykey存在, 则ok为true。 当然如果只想确认一个key是否存在, 而不关心这个key对应的value的话, 那么用_, ok = m[“mykey”], 然后判断ok是否为true就行了。

如果你想要遍历map里面的每一个元素, 你可以试用range语句:

for key, value := range m {    fmt.Println("Key:", key, "Value:", value)}

也可以在声明的时候直接把一些元素初始化出来:

commits := map[string]int{    "rsc": 3711,    "r":   2138,    "gri": 1908,    "adg": 912,}

利用零值

我们可以利用“map在key不存在的情况下返回value的零值”这个特性, 做一些事情。 比如我们可以用map来模拟集合,valueType为布尔类型(布尔类型的零值是false)。下面的例子遍历Node类型的链表,并且打印遍历的值,它利用一个Node指针的map来避免出现环形链表(这样遍历永远不会结束)。

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)    }

表达式visisted[n]的值为true如果n被访问过, 为false如果n没有被访问过。 我们不用那种_, ok = visited[n], 然后判断ok的方式来判断n是否出现过, 利用布尔类型的零值为false可以像上面的代码一样简洁的把这件事情干了。

另外一个利用零值的例子是一个slice的map, append一个nil的slice会自动分配一个新的slice,所以没有必要先判断slice是否为空再append。在下面的例子中,people是一个slice,里面放着结构为Person的数据, Person有Name属性和Likes属性, Likes是这个人的喜好的slice。

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) // no need to check if like[l] is nil        }    }

这样打印喜欢cheese的人的列表:

for _, p := range likes["cheese"] {        fmt.Println(p.Name, "likes cheese.")    }

打印有多少人喜欢bacon:

fmt.Println(len(likes["bacon"]), "people like bacon.")

key的类型

上面我们说过, map的key必须是 可比较 的(其实自己实现过hash表的人也应该知道, 我们一般对key做hash,然后把hash相同的key放到一个桶上, 有冲突的时候可以挂链,在遍历这个链的时候需要比较key才能找到这个key), 语言规范 对可以比较做了准确的描述,用一个简短的话来说, 布尔型, 数值,字符串,指针,channel和interface都是可以比较的,只包含以上类型的数组或者struct也是可比较的。从上面可以看到, slices,map,函数,都是不可比较的, 所以不能用作map的key(但是可以作为map的value)。 很明显数值类型,字符串类型和其他基本类型可以用来做key,但是struct做key不是特别明显,我们就用一个列子来说明有些时候用struct做key可以使代码更简单。下面的结构(map的map)可以用来按国家统计一个url被访问的次数。

hits := make(map[string]map[string]int)

这个是一个字符串到(字符串到int的map)的map,外层的key表示一个url, 里层的map的key表示国家。下面的代码用来取澳大利亚访问/doc的次数:

n := hits["/doc/"]["au"]

当我们更新一个国家的访问次数的时候, 会很麻烦, 对于每一个外层的key,我们都得判断里层的map是否存在, 如果不存在, 我们要新建一个map:

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")

如果我们用一个struct做key的话, 就不用这么麻烦的判断内层的map是否为空了:

type Key struct {    Path, Country string}hits := make(map[Key]int)hits[Key{"/doc", "au"}]++

取澳大利亚访问/doc的次数也很简单:

n := hits[Key{"/doc", "au"}]

map不是并发访问安全的 ,同时读写同一个map的行为是未定义的,所以你要在多个goroutine里面操作同一个map,需要同步原语来保护的, 比较常见的方法是用 sync.RWMutex . 。 下面的代码定义了一个counter,这个counter是一个匿名struct类型的,这个匿名struct里面就包含了一个map和 嵌入 的sync.RWMutex . 。

var counter = struct{    sync.RWMutex    m map[string]int}{m: make(map[string]int)}

在读的时候, 需要加读锁:

counter.RLock()n := counter.m["some_key"]counter.RUnlock()fmt.Println("some_key:", n)

在写的时候, 需要加写锁:

counter.Lock()counter.m["some_key"]++counter.Unlock()

遍历顺序

map遍历的时候,返回的key顺序是 随机 的,而且不保证两次遍历的顺序是相同的, 不要依赖于这个顺序。

本文主要参考的是 https://blog.golang.org/go-maps-in-action, 如有说的不对的地方, 欢迎指正。

1 0
原创粉丝点击