go map

简介

哈希表是计算机领域应用最广泛的数据结构之一。通常情况下,哈希表都会提供快速增/删/改/查数据的操作。go语言中也对哈希表做了支持,也就是map结构。

声明和初始化

map的声明方式如下,其中keyType应该是可比较的数据,而valueType可以是任何类型,甚至可以是一个map

map[keyType]valueType

接下来,声明一个key值为string,value值为int的map —> m

var m map[string]int

map是一个引用类型,如果只声明而没有初始化,则其值为nil,表现为一个empty map,但是需要注意的是,不能直接使用这个map来执行以下赋值操作

m["test"] = 5  //直接使用这个赋值会报错 --> panic: assignment to entry in nil map  

赋值操作前,需要初始化map,如下

m = make(map[string]int)

make函数会返回一个map对象并赋值给m,关于map的具体实现本篇不多做探讨,仅讨论map的具体用法

当然,如果不使用make函数,也可以使用如下方式初始化map

m = map[string]int{}

如果需要添加初始值的话,也可以在大括号{}中以key : value的形式添加初始值

m = map[string]int{
   "liming":   88,
   "lihua":    99,
   "xiaohong": 100,
}

map的使用

map的赋值或修改操作如下

如果map中不包含 key 值为 “liming” 的数据,则会新增加一个 key 值为 ”liming“ value 值为 87 的 entry

如果map中包含 key 值为 “liming” 的数据,则会将 key 值为 “liming” 的数据 value 值修改为 87

m["liming"] = 87  //map --> map[lihua:99 liming:88 xiaohong:100]     

获取map中的value值操作如下

如果map中不包含key值为 ”liming“ 的数据,则会获取一个默认值 0 (这里获取默认值为0是因为value类型为 int 如果是其他类型则获取其他类型的默认值)

score := m["liming"]

删除map中的元素

执行delete后,将会删除map中key值为“wang”的数据,这里需要了解的是,如果map中不存在这个key值,delete函数不会做任何操作

delete(m, "wang")

获取map的长度

n := len(m) // n == 3

map值的获取实际上返回的是两个值,如下

score获取key值“liming” 在map中对应的value类型的值,若key值不存在,则score为0(int默认值),ok则是一个bool类型,用来判断key值是否存在于map,如果存在则 ok 为true,如果不存在则 ok 为false

score, ok := m["liming"]  //score == 87, ok == true  

如果仅需判断key值是否存在,也可以使用符号 ‘_’ 来代替score变量,这样就可以忽略对value的使用

_, ok := m["liming"]

使用for循环遍历map

for key, value := range m {
   fmt.Println("key:", key, "value:", value)
}
//key: xiaohong value: 100                              
//key: liming value: 87                                 
//key: lihua value: 99 

默认值的更多用法

循环链表的检测

map中获取不存在key的value值,会返回对应类型的默认值,利用这一特性,可以用来检测循环链表是否遍历完成

以下程序中,如果visited[n] 中的n不存在于map,则会返回false,遍历过这个值后,将值存入map,后续visited[n]就会返回true,循环下来到起始位置,则visited[n]必定返回true,遍历到此结束

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)
}
map中slices的默认值

在经过make函数初始化map后,以下程序默认的value值会是一个空的slices(这个也是nil值,但是支持slices的append等操作,slices不像map需要初始化,只需要声明即可使用),用来存储对应的数据

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

map中的key

前面说道,只要是可比的类型,都可以当做map的key值,在go中,可比较类型主要有 boolean, numeric, string, pointer, channel,interface types, 另外 structs 或者 arrays 如果只包含上述类型的话,也可以当做key值。但slices,map以及function就不可以作为key使用

map的并发

map并不是线程安全的实现,原因在官方文档解释如下

### Why are map operations not defined to be atomic?

After long discussion it was decided that the typical use of maps did not require safe access from multiple goroutines, and in those cases where it did, the map was probably part of some larger data structure or computation that was already synchronized. Therefore requiring that all map operations grab a mutex would slow down most programs and add safety to few. This was not an easy decision, however, since it means uncontrolled map access can crash the program.

The language does not preclude atomic map updates. When required, such as when hosting an untrusted program, the implementation could interlock map access.

Map access is unsafe only when updates are occurring. As long as all goroutines are only reading—looking up elements in the map, including iterating through it using a `for` `range` loop—and not changing the map by assigning to elements or doing deletions, it is safe for them to access the map concurrently without synchronization.

As an aid to correct map use, some implementations of the language contain a special check that automatically reports at run time when a map is modified unsafely by concurrent execution.

大致意思为

  1. map可能应用于已经实现了并发的程序中,另外如果实现map的线程安全,将会极大拖慢程序效率,并且只得到较小的安全性提升
  2. map的并发不安全主要体现在更新操作中,但是map一般的操作都是读取数据,这类操作在并发中是安全的
  3. go的一些特殊实现会有安全检查,当map执行了并发不安全操作时,会自动报错

如果平时使用map时候必须实现线程安全的话,可以使用 sync.RWMutex

声明一个匿名struct,其中包含内置的 sync.RWMutex以及一个map

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的遍历

在使用map的过程中,应该不难发现,map遍历出来的数据并不是每次都是相同的顺序,如果需要指定顺序的话,就需要将map的key值用另一个可以支持排序的数据结构保存起来,这时,想要顺序遍历map就可以通过顺序获取key,再通过指定key来获取map中的值

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

参考

Go maps in action

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值