Go 语言中的 Map

本文介绍一种特殊的数据结构。它是一种元素对的无序集合,每一个索引(key)对应一个值(value),这种数据结构在 Go 语言中被称之为 mapmap 是一种能够通过索引(key)迅速找到值(value)的数据结构,所以也被称为字典。在 Go 语言中因为线程安全问题,一共实现了两种类型的 map,接下来我们每种都了解一下。

Tips:线程的知识会在Go语言的多线程中讲解。

 

1. 无锁的map

这种类型的 map 是线程不安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,会有读写冲突,会导致系统奔溃。所以一般在单线程程序中使用的较多。

 

1.1 map 的创建

map 的底层结构也是一个指针,所以和变量不同,并不是声明后立刻能够使用。和切片相同,需要使用make()函数进行初始化。在初始化之前为空,没有零值。

代码示例:

代码块
  • package main
  • 2
  • import (
  • 4          "fmt"
  • )
  • 6
  • func main() {
  • 8          var m map[string]string
  • 9          fmt.Println(m == nil)
  • 10        m = make(map[string]string)
  • 11        fmt.Println(m == nil)
  • 12  }
  • 第 8 行:声明一个 key 为 string 类型,value 为 string 类型的 map 变量;
  • 第 9 行:此时 m 未初始化,值为 nil;
  • 第 10 行:初始化 m。
  • 第 11 行:此时 m 是一个没有存放数据的 map,值不为 nil。

执行结果:

图片描述

 

1.2 map 的赋值

map 的赋值有两种方式:

  • 使用:=使map在定义的时候直接赋值;
  • 使用map[key]=value的形式对map进行赋值。

在明确知道 map 的值的时候就可以使用第一种方式进行赋值,比如说在建立中英文对应关系的时候。在未知 map 的取值时,一般建议使用后者进行赋值。

代码示例:

代码块
  • package main
  • 2
  • import "fmt"
  • 4
  • func main() {
  • 6          m1 := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
  • 7          fmt.Println(m1["Apple"])
  • 8          m2 := make(map[string]string)
  • 9          m2["Apple"] = "苹果"
  • 10        m2["Orange"] = "橘子"
  • 11        m2["Banana"] = "香蕉"
  • 12        fmt.Println(m2["Apple"])
  • 13  }
  • 第 6 行:在 m1 被定义的时候直接赋值;
  • 第 7 行:输出 m 1中 key 为 “Apple” 时对应的值;
  • 第 8 行:使用:=进行免声明 make;
  • 第 9~11 行:对 m2 进行赋值;
  • 第 12 行:输出 m2 中 key 为 “Apple” 时对应的值。

执行结果:

图片描述

 

1.3 map 的遍历

map 是字典结构,如果不清楚所有 key 的值,是无法对 map 进行遍历的,所以 Go 语言中使用了一个叫做range的关键字,配合for循环结构来对map结构进行遍历。

Tips:range同时也可以用来遍历数组和切片,数组和切片在range中可以看为map[int]数据类型结构,遍历和用法和map一致。

代码示例:

代码块
  • package main
  • 2
  • import "fmt"
  • 4
  • func main() {
  • 6          m := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
  • 7          for k, v := range m {
  • 8                    fmt.Println("key:", k, ", value:", v)
  • 9          }
  • 10  }
  • 第 7 行:使用 range 关键字,每次 for 循环都会取出一个不重复的 key 和 value,赋值给 k 和 v,直至循环结束。

Tips:map 是无序的,所以每次输出的顺序可能会不一样。

执行结果:

图片描述

 

1.4 map 的删除

map 在普通的用法中是无法移除只可以增加 key 和 value 的,所以 Go 语言中使用了一个内置函数delete(map,key)来移除 map 中的 key 和 value。

代码示例:

代码块
  • package main
  • 2
  • import "fmt"
  • 4
  • func main() {
  • 6            m := map[string]string{"Apple": "苹果", "Orange": "橘子", "Banana": "香蕉"}
  • 7            fmt.Println(m)
  • 8            delete(m, "Apple")
  • 9            fmt.Println(m)
  • 10  }
  • 第8行:删除 m 中的 “Apple” 和其对应的 value。

执行结果:

图片描述

 

2. 自带锁的 sync.Map

这种类型的 map 是线程安全的 map,多个线程同时访问这个类型的 map 的同一个变量时,不会有读写冲突,因为它自带原子锁,保障了多线程的数据安全。

 

2.1 sync.Map 的创建

这种类型的 map 创建不需要make,直接声明就可以使用,而且不需要声明 map 的 key 和 value 的类型。因为它底层的实现并不是指针,是一种多个变量的聚合类型,叫做结构体

Tips:结构体的概念会在Go语言的结构体中讲解

代码示例:

代码块
  • package main
  • 2
  • import (
  • 4          "fmt"
  • 5          "sync"
  • )
  • 7
  • 8  func main() {
  • 9          var m sync.Map
  • 10        fmt.Println(m)
  • 11  }
  • 第 9 行:声明一个 sync.Map。
  • 第 10 行:输出 m 的零值。

执行结果:

图片描述

 

2.2 sync.Map 的操作

这个类型关于 map 的所有操作都是使用它自带的方法来实现的。包括range

代码示例:

代码块
  • package main
  • 2
  • import (
  • 4          "fmt"
  • 5          "sync"
  • )
  • 7
  • func main() {
  • 9
  • 10        var m sync.Map
  • 11        m.Store("Apple", "苹果")
  • 12        m.Store("Orange", "橘子")
  • 13        m.Store("Banana", "香蕉")
  • 14        tmp, exist := m.Load("Orange")
  • 15        fmt.Println(tmp, exist)
  • 16
  • 17        m.Delete("Banana")
  • 18
  • 19        m.Range(func(k, v interface{}) bool {
  • 20               fmt.Println("key:", k, ", value:", v)
  • 21               return true
  • 22        })
  • 23  }
  • 第 11~13 行:使用 Store 方法给 m 赋值;
  • 第 14 行:使用 Load 取出 “Orange” 对应的值,如果不存在 “Orange” 这个 key,exist 的值为 false;
  • 第 17 行:删除 m 中的 “Banana” 和其对应的 value;
  • 第 19 行:使用 Range 方法遍历 m。

执行结果:

图片描述

 

3. 小结

本文主要讲解了两个 map 数据类型,两种在功能上区别并不大,主要是在应用上。map[数据类型]数据类型一般使用在单线程场景,多线程场景使用sync.Map。在赋值上map[数据类型]数据类型可以赋初值,且需要指定数据类型。sync.Map无法赋初值,无需指定数据类型。

文章来源于网络,侵删!

相关新闻