go语言map(go语言映射)

Go 语言提供的映射关系容器为 map,map使用散列表(hash)实现。

添加关联到 map 并访问关联和数据

Go 语言中 map 的定义是这样的:
map[KeyType]ValueType

  • KeyType为键类型。
  • ValueType是键对应的值类型。

大多数语言中映射关系容器使用两种算法:散列表和平衡树。
扩充 散列表可以简单描述为一个数组(俗称“桶”),数组的每个元素是一个列表。根据散列函数获得每个元素的特征值,将特征值作为映射的键。如果特征值重复,表示元素发生碰撞。碰撞的元素将被放在同一个特征值的列表中进行保存。散列表查找复杂度为 O(1),和数组一致。最坏的情况为 O(n),n 为元素总数。散列需要尽量避免元素碰撞以提高查找效率,这样就需要对“桶”进行扩容,每次扩容,元素需要重新放入桶中,较为耗时。

平衡树类似于有父子关系的一棵数据树,每个元素在放入树时,都要与一些节点进行比较。平衡树的查找复杂度始终为 O(log n)。
下面代码展示了 map 的基本使用环境。

scene := make(map[string]int)

scene["route"] = 66

fmt.Println(scene["route"])

v := scene["route2"]
fmt.Println(v)

代码输出如下:

66
0
  • 第 1 行 map 是一个内部实现的类型,使用时,需要手动使用 make 创建。如果不创建使用 map 类型,会触发宕机错误。
  • 第 3 行向 map 中加入映射关系。写法与使用数组一样,key 可以使用除函数以外的任意类型。
  • 第 7 行中,尝试查找一个不存在的键,那么返回的将是 ValueType 的默认值。

某些情况下,需要明确知道查询中某个键是否在 map 中存在,可以使用一种特殊的写法来实现,看下面的代码:
v, ok := scene["route"]

  • 在默认获取键值的基础上,多取了一个变量 ok,可以判断键 route 是否存在于 map 中。

map 还有一种在声明时填充内容的方式,代码如下:

m := map[string]string{
    "W": "forward",
    "A": "left",
    "D": "right",
    "S": "backward",
}

例子中并没有使用 make,而是使用大括号进行内容定义,就像 JSON 格式一样,冒号的左边是 key,右边是值,键值对之间使用逗号分隔。

Go语言遍历map(访问map中的每一个键值对)

map 的遍历过程使用 for range 循环完成,代码如下:

scene := make(map[string]int)
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
for k, v := range scene {
    fmt.Println(k, v)
}

遍历对于 Go 语言的很多对象来说都是差不多的,直接使用 for range 语法。遍历时,可以同时获得键和值。如只遍历值,可以使用下面的形式:
for _, v := range scene {

  • 将不需要的键改为匿名变量形式。

只遍历键时,使用下面的形式
for k := range scene {

  • 无须将值改为匿名变量形式,忽略值即可

注意 遍历输出元素的顺序与填充顺序无关。不能期望 map 在遍历时返回某种期望顺序的结果。
map是无序的

如果需要特定顺序的遍历结果,正确的做法是排序,代码如下:

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
// 声明一个切片保存map数据
var sceneList []string
// 将map数据遍历复制到切片中
for k := range scene {
    sceneList = append(sceneList, k)
}
// 对切片进行排序
sort.Strings(sceneList)
// 输出
fmt.Println(sceneList)

使用 delete() 函数从 map 中删除键值对

使用 delete() 内建函数从 map 中删除一组键值对,delete() 函数的格式如下:
delete(map, 键)

  • map 为要删除的 map 实例。
  • 键为要删除的 map 键值对中的键。

从 map 中删除一组键值对可以通过下面的代码来完成:

scene := make(map[string]int)
// 准备map数据
scene["route"] = 66
scene["brazil"] = 4
scene["china"] = 960
delete(scene, "brazil")
for k, v := range scene {
    fmt.Println(k, v)
}

清空 map 中的所有元素

有意思的是,Go 语言中并没有为 map 提供任何清空所有元素的函数、方法。清空 map 的唯一办法就是重新 make 一个新的 map。不用担心垃圾回收的效率,Go 语言中的并行垃圾回收效率比写一个清空函数高效多了。

Go语言sync.Map(在并发环境中使用的map)

Go 语言中的 map 在并发情况下,只读是线程安全的,同时读写线程不安全。
下面来看下并发情况下读写 map 时会出现的问题,代码如下:

// 创建一个int到int的映射
m := make(map[int]int)
// 开启一段并发代码
go func() {
    // 不停地对map进行写入
    for {
        m[1] = 1
    }
}()
// 开启一段并发代码
go func() {
    // 不停地对map进行读取
    for {
        _ = m[1]
    }
}()
// 无限循环, 让并发程序在后台执行
for {
}

运行代码会报错,输出如下:
fatal error: concurrent map read and map write

运行时输出提示:并发的 map 读写。也就是说使用了两个并发函数不断地对 map 进行读和写而发生了竞态问题。map 内部会对这种并发操作进行检查并提前发现。

需要并发读写时,一般的做法是加锁,但这样性能并不高。Go 语言在 1.9 版本中提供了一种效率较高的并发安全的 sync.Map。sync.Map 和 map 不同,不是以语言原生形态提供,而是在 sync 包下的特殊结构。
sync.Map有以下特性:

  • 无须初始化,直接声明即可。
  • sync.Map 不能使用 map 的方式进行取值和设置等操作,而是使用 sync.Map 的方法进行调用。Store 表示存储,Load 表示获取,Delete 表示删除。
  • 使用 Range 配合一个回调函数进行遍历操作,通过回调函数返回内部遍历出来的值。Range 参数中的回调函数的返回值功能是:需要继续迭代遍历时,返回 true;终止迭代遍历时,返回 false。

并发安全的 sync.Map 演示代码如下:

package main
import (
      "fmt"
      "sync"
)
func main() {
    var scene sync.Map
    // 将键值对保存到sync.Map
    scene.Store("greece", 97)
    scene.Store("london", 100)
    scene.Store("egypt", 200)
    // 从sync.Map中根据键取值
    fmt.Println(scene.Load("london"))
    // 根据键删除对应的键值对
    scene.Delete("london")
    // 遍历所有sync.Map中的键值对
    scene.Range(func(k, v interface{}) bool {
        fmt.Println("iterate:", k, v)
        return true
    })
}

sync.Map 没有提供获取 map 数量的方法,替代方法是获取时遍历自行计算数量。sync.Map 为了保证并发安全有一些性能损失,因此在非并发情况下,使用 map 相比使用 sync.Map 会有更好的性能。

你可能感兴趣的:(go语言学习)