在实际编程开发中,无论在任何语言里面,除了数组之外,最常用的数据结构莫非map,map是一种数据结构,在很多语言里面都内置了map类型,map一般被翻译成映射或者地图。从数据结构定义上,map是一组键-值对的集合,例如:
1 | 张三: 18 |
map强调的是一一对应,在具体实现上,不同语言略有差异,比如弱类型语言里面就不会限制key或value的类型,但是在强类型语言比如Go里面,键值的类型是需要声明限定。
但是需要注意的是,map结构的key是唯一的,但是value可以重复。
说到实现原理,大多数语言里面map都是基于hash结构实现,为了解决hash冲突问题,又引入开放地址法和链表结构,Go也不例外,这一点大家简单了解一下即可,并不影响咱们使用,下面咱就结合Go语言来看看map的常见用法。
1.基本语法
1 | package main |
map的使用特别简单,使用make初始化,和Go里面大多数类型一样,也可以使用字面量初始化,这里不多说。
2.快速查找
Hash这种数据结构的查找时间复杂度是O(1),map也继承了这个优点,所以当你知道key,想在map里面查找一个元素,那是相当快滴。
1 | func find() { |
值得注意的是,如果你访问一个不存在的key,默认会返回零值,所以为了靠谱的判断map里面是否包含某key,可以使用 v, ok := m[key]
这种写法。
3.去重计数
由于map的key是唯一的,如果你给map里面的某个key多次赋值,会以最后一次为准,所以利用这个特性,我们可以用来给一组数据做去重,顺便计数,效率也很高,举例说明:
1 | package main |
下面开始划重点,map的key不仅仅可以是普通类型,也可以是结构体类型,所以我们不仅可以针对普通类型做去重,可以对结构体做去重,而结构体可以有多个成员,举个例子,根据名字+年龄对数据进行去重,这个用法很多人可能不了解。
1 | package main |
结果如下: map[{小六 20}:1 {小六 25}:2 {张三 20}:1 {张三 25}:2 {李四 20}:1 {李四 25}:2 {王五 20}:2 {王五 25}:4]
4.同步map
在Go的开发中,map经常也会拿来当key-value临时缓存使用,虽然是单机缓存,但是效率也很高,不过当多个协程同时读写map的时候存在一个并发问题。
1 | package main |
上面的代码实际运行中,有大概率会出现fatal error: concurrent map read and map write,除非你设置runtime.GOMAXPROCS(1)
,这相当于开启了单线程模式。
为了解决问题,有两种方案,一种是自己手动加锁,利用sync包里面的排它锁,在读写操作之前先加锁,操作完之后解锁,示例:
1 | go func() { |
另一种方案则是使用sync.Map,这个数据结构提供了 load、store这2个方法用于读写map,需要注意的是这个2个方法的参数都是 interface{} 类型,所以在使用的时候需要做断言处理,稍微有点不便,这也是没办法,因为Go没有泛型。
1 | m := sync.Map{} |