Go 語言中的 Map
本文介紹一種特殊的數據結構。它是一種元素對的無序集合,每一個索引(key)對應一個值(value),這種數據結構在 Go 語言中被稱之為?map。map?是一種能夠通過索引(key)迅速找到值(value)的數據結構,所以也被稱為字典。在 Go 語言中因為線程安全問題,一共實現了兩種類型的 map,接下來我們每種都了解一下。
Tips:線程的知識會在Go語言的多線程中講解。
1. 無鎖的map
這種類型的 map 是線程不安全的 map,多個線程同時訪問這個類型的 map 的同一個變量時,會有讀寫沖突,會導致系統奔潰。所以一般在單線程程序中使用的較多。
1.1 map 的創建
map 的底層結構也是一個指針,所以和變量不同,并不是聲明后立刻能夠使用。和切片相同,需要使用make()函數進行初始化。在初始化之前為空,沒有零值。
代碼示例:
- 1?
package main
- 2
- 3?
import (
- 4? ? ? ? ?
"fmt"
- 5?
)
- 6
- 7?
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 的取值時,一般建議使用后者進行賦值。
代碼示例:
- 1?
package main
- 2
- 3?
import "fmt"
- 4
- 5?
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一致。
代碼示例:
- 1?
package main
- 2
- 3?
import "fmt"
- 4
- 5?
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。
代碼示例:
- 1?
package main
- 2
- 3?
import "fmt"
- 4
- 5?
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語言的結構體中講解
代碼示例:
- 1?
package main
- 2
- 3?
import (
- 4? ? ? ? ?
"fmt"
- 5? ? ? ? ?
"sync"
- 6?
)
- 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。
代碼示例:
- 1?
package main
- 2
- 3?
import (
- 4? ? ? ? ?
"fmt"
- 5? ? ? ? ?
"sync"
- 6?
)
- 7
- 8?
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
無法賦初值,無需指定數據類型。
文章來源于網絡,侵刪!