0x01 map基本操作
// 1. 聲明
var m map[string]int
// 2. 初始化,聲明之後必須初始化才能使用
// 向未初始化的map賦值引起 panic: assign to entry in nil map.
m = make(map[string]int)
m = map[string]int{}
// 1&2. 聲明並初始化
m := make(map[string]int)
m := map[string]int{}
// 3. 增刪改查
m["route"] = 66
delete(m, "route") // 如果key不存在什麼都不做
i := m["route"] // 三種查詢方式,如果key不存在返回value類型的零值
i, ok := m["route"]
_, ok := m["route"]
// 4. 迭代(順序不確定)
for k, v := range m {
use(k, v)
}
// 5. 有序迭代
import "sort"
var keys []string
for k, _ := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
use(k, m[k]
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
0x02 map鍵類型
The key can be any type for which the equality operator is defined.
支持 == 操作符的類型有:
- boolean,
- numeric,
- string,
- pointer,
- channel,
- interface(as long as dynamic type supports equality),
- 以及只包含上述類型的array和struct
不支持 == 操作符的類型有:
- slice,
- map,
- func,
補充
- 不像Java可以爲class自定義hashcode方法,以及C++可以重載==操作符,golang map**不支持**==重載或者使用自定義的hash方法。因此,如果想要把struct用作map的key,就必須保證struct不包含slice, map, func
- golang爲uint32、uint64、string提供了fast access,使用這些類型作爲key可以提高map訪問速度,詳見hashmap_fast.go
0x03 map併發
map不是併發安全的,通常使用sync.RWMutex保護併發map
// 聲明&初始化
var counter = struct {
sync.RWMutex // gard m
m map[string]int
}{m:make(map[string]int)}
// 讀鎖
counter.RLock()
counter.m["route"]
counter.RUnlock()
// 寫鎖
counter.Lock()
counter.m["route"]++
counter.Unlock()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
0x04 map小技巧
4.1. 利用value類型的零值
visited := map[*Node]bool
if visited[node] { // bool類型0值爲false,所以不需要檢查ok
return
}
likes := make(map[string][]*Person)
for _, p range people {
for _, l range p.Likes {
// 向一個nil的slice增加值,會自動allocate一個slice
likes[l] = append(likes[l], p)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4.2. map[k1]map[k2]v 對比 map[struct{k1, k2}]v
// map[k1]map[k2]v
hits := make(map[string]map[string]int)
func add(m map[string]map[string]int, path, country string) {
mm, ok := m[path]
if !ok {
mm = make(map[string]int) // 需要檢查、創建子map
m[path] = mm
}
mm[country]++
}
add(hits, "/", "cn")
n := hits["/"]["cn"]
// map[struct{k1, k2}]v
type Key struct {
Path, Country string
}
hits := make(map[Key]int)
hits[Key{"/", "cn"}]++
n := hits[Key{"/", "cn"}]
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
0x05 map實現細節淺析
5.1. 如何計算hash值
golang爲每個類型定義了類型描述器_type
,並實現了hashable類型的_type.alg.hash
和_type.alg.equal
。
type typeAlg struct {
// function for hashing objects of this type
// (ptr to object, seed) -> hash
hash func(unsafe.Pointer, uintptr) uintptr
// function for comparing objects of this type
// (ptr to object A, ptr to object B) -> ==?
equal func(unsafe.Pointer, unsafe.Pointer) bool
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
5.2. map實現結構
map的實現主要有三個struct,
- maptype用來保存map的類型信息,包括key、elem(value)的類型描述器,keysize,valuesize,bucketsize等;
- hmap - A header for a Go map. hmap保存了map的實例信息,包括count,buckets,oldbuckets等;buckets是bucket的首地址,用hash值的低h.B位
hash & (uintptr(1)<<h.B - 1)
計算出key所在bucket的index; - bmap - A bucket for a go map. bmap只有一個域
tophash [bucketCnt]uint8
,它保存了key的hash值的高8位uint8(hash >> (sys.PtrSize*8 - 8))
;一個bucket包括一個bmap(tophash數組),緊跟的bucketCnt個keys和bucketCnt個values,以及一個overfolw指針。
makemap根據maptype中的信息初始化hmap
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hmap {
...
// initialize Hmap
if h == nil {
h = (*hmap)(newobject(t.hmap))
}
h.count = 0
h.B = B
h.flags = 0
h.hash0 = fastrand()
h.buckets = buckets
h.oldbuckets = nil
h.nevacuate = 0
h.noverflow = 0
return h
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
5.3. 如何訪問map
golang的maptype保存了key的類型描述器,以供訪問map時調用key.alg.hash
, key.alg.equal
。
type maptype struct {
key *_type
elem *_type
...
}
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
...
// 併發訪問檢查
if h.flags&hashWriting != 0 {
throw("concurrent map read and map write")
}
// 計算key的hash值
alg := t.key.alg
hash := alg.hash(key, uintptr(h.hash0)) // alg.hash
// 計算key所在的bucket的index
m := uintptr(1)<<h.B - 1
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
// 計算tophash
top := uint8(hash >> (sys.PtrSize*8 - 8))
...
for {
for i := uintptr(0); i < bucketCnt; i++ {
// 檢查top值
if b.tophash[i] != top {
continue
}
// 取key的地址
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
if alg.equal(key, k) { // alg.equal
// 取value得地址
v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
}
}
...
if b == nil {
// 返回零值
return unsafe.Pointer(&zeroVal[0])
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
5.4. map擴張
這部分留待以後有機會再續。這裏暫時附上Keith Randall的slide作爲參考。
0x06 map建議
- 如果知道size,預先分配資源
make(map[int]int, 1000)
- uint32, uint64, string作爲鍵,非常快
- 清理map:
for k:= range m { delete(m, k) }
- key和value中沒有指針可以使GC scanning更快
https://blog.csdn.net/Soooooooo8/article/details/70163475
參考
Go msps in action
hashmap.go type.go
GopherCon 2016: Keith Randall - Inside the Map Implementation