1. 程式人生 > GO語言入門 >28 Go 語言中的 Map

28 Go 語言中的 Map

本文介紹一種特殊的資料結構。它是一種元素對的無序集合,每一個索引(key)對應一個值(value),這種資料結構在 Go 語言中被稱之為 mapmap 是一種能夠通過索引(key)迅速找到值(value)的資料結構,所以也被稱為字典。在 Go 語言中因為執行緒安全問題,一共實現了兩種型別的 map,接下來我們每種都瞭解一下。

Tips:執行緒的知識會在Go語言的多執行緒中講解。

1. 無鎖的map

這種型別的 map 是執行緒不安全的 map,多個執行緒同時訪問這個型別的 map 的同一個變數時,會有讀寫衝突,會導致系統奔潰。所以一般在單執行緒程式中使用的較多。

1.1 map 的建立

map 的底層結構也是一個指標,所以和變數不同,並不是聲明後立刻能夠使用。和切片相同,需要使用make()函式進行初始化。在初始化之前為空,沒有零值。

程式碼示例:

package main

import (
    "fmt"
)

func main() {
    var m map[string]string
    fmt.Println(m == nil)
    m = make(map[string]string)
    fmt.Println(m == nil)
}
  • 第 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

import "fmt"

func main() {
    m1 := map[string]string{"Apple": "蘋果", "Orange": "橘子", "Banana": "香蕉"}
    fmt.Println(
m1["Apple"]) m2 := make(map[string]string) m2["Apple"] = "蘋果" m2["Orange"] = "橘子" m2["Banana"] = "香蕉" fmt.Println(m2["Apple"]) }
  • 第 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

import "fmt"

func main() {
    m := map[string]string{"Apple": "蘋果", "Orange": "橘子", "Banana": "香蕉"}
    for k, v := range m {
        fmt.Println("key:", k, ", value:", v)
    }
}
  • 第 7 行:使用 range 關鍵字,每次 for 迴圈都會取出一個不重複的 key 和 value,賦值給 k 和 v,直至迴圈結束。

Tips:map 是無序的,所以每次輸出的順序可能會不一樣。

執行結果:

圖片描述

1.4 map 的刪除

map 在普通的用法中是無法移除只可以增加 key 和 value 的,所以 Go 語言中使用了一個內建函式delete(map,key)來移除 map 中的 key 和 value。

程式碼示例:

package main

import "fmt"

func main() {
    m := map[string]string{"Apple": "蘋果", "Orange": "橘子", "Banana": "香蕉"}
    fmt.Println(m)
    delete(m, "Apple")
    fmt.Println(m)
}
  • 第8行:刪除 m 中的 “Apple” 和其對應的 value。

執行結果:

圖片描述

2. 自帶鎖的 sync.Map

這種型別的 map 是執行緒安全的 map,多個執行緒同時訪問這個型別的 map 的同一個變數時,不會有讀寫衝突,因為它自帶原子鎖,保障了多執行緒的資料安全。

2.1 sync.Map 的建立

這種型別的 map 建立不需要make,直接宣告就可以使用,而且不需要宣告 map 的 key 和 value 的型別。因為它底層的實現並不是指標,是一種多個變數的聚合型別,叫做結構體

Tips:結構體的概念會在Go語言的結構體中講解

程式碼示例:

package main

import (
    "fmt"
    "sync"
)

func main() {
    var m sync.Map
    fmt.Println(m)
}
  • 第 9 行:宣告一個 sync.Map。
  • 第 10 行:輸出 m 的零值。

執行結果:

圖片描述

2.2 sync.Map 的操作

這個型別關於 map 的所有操作都是使用它自帶的方法來實現的。包括range

程式碼示例:

package main

import (
    "fmt"
    "sync"
)

func main() {

    var m sync.Map
    m.Store("Apple", "蘋果")
    m.Store("Orange", "橘子")
    m.Store("Banana", "香蕉")
    tmp, exist := m.Load("Orange")
    fmt.Println(tmp, exist)

    m.Delete("Banana")

    m.Range(func(k, v interface{}) bool {
        fmt.Println("key:", k, ", value:", v)
        return true
    })
}
  • 第 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無法賦初值,無需指定資料型別。