Hashmap
基本語法
定義hashmap變數
由於go語言是一個強型別的語言,因此hashmap也是有型別的,具體體現在key和value都必須指定型別,比如宣告一個key為string,value也是string的map,
需要這樣做
var m map[string]string // 宣告一個hashmap,還不能直接使用,必須使用make來初始化 m = make(map[string]string) // 初始化一個map m = make(map[string]string, 3) // 初始化一個map並附帶一個可選的初始bucket(非準確值,只是有提示意義) m := map[string]string{} // 宣告並初始化 m := make(map[string]string) // 使用make來初始化
大部分型別都能做key,某些型別是不能的,共同的特點是:不能使用== 來比較,包括: slice, map, function
get,set,delete
m := map[string]int m["a"] = 1 fmt.Println(m["a"]) // 輸出 1 // 如果訪問一個不存在的key,返回型別預設值 fmt.Println(m["b"]) // 輸出0 // 測試key是否存在 v, ok := m["b"] if ok { ... } // 刪除一個key delete(m, "a")
迭代器
// 只迭代key for k := range m { ... } // 同時迭代key-value for k, v := range m { ... }
在迭代的過程中是可以對map進行刪除和更新操作的,規則如下:
- 迭代是無序的,跟插入是的順序無關
- 迭代的過程中刪除一個key,無論遍歷還是沒有遍歷過都不會再遍歷到
- 迭代的過程中新增一個key,不確定是否能遍歷到
- 未初始化的map也可以迭代
其他
- map的value是不可取地址的,意味著 &m["a"]這樣的語法是非法的
- len可以獲取當前map的kv個數
內部結構
hashmap結構
golang的map是hash結構的,意味著平均訪問時間是O(1)的。同傳統的hashmap一樣,由一個個bucket組成:
// A header for a Go map. type hmap struct { // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and // ../reflect/type.go.Don't change this structure without also changing that code! count int // # live cells == size of map.Must be first (used by len() builtin) flags uint8 Buint8// log_2 of # of buckets (can hold up to loadFactor * 2^B items) hash0 uint32 // hash seed bucketsunsafe.Pointer // array of 2^B Buckets. may be nil if count==0. oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing nevacuateuintptr// progress counter for evacuation (buckets less than this have been evacuated) // If both key and value do not contain pointers and are inline, then we mark bucket // type as containing no pointers. This avoids scanning such maps. // However, bmap.overflow is a pointer. In order to keep overflow buckets // alive, we store pointers to all overflow buckets in hmap.overflow. // Overflow is used only if key and value do not contain pointers. // overflow[0] contains overflow buckets for hmap.buckets. // overflow[1] contains overflow buckets for hmap.oldbuckets. // The first indirection allows us to reduce static size of hmap. // The second indirection allows to store a pointer to the slice in hiter. overflow *[2]*[]*bmap }
bucket內部
// A bucket for a Go map. type bmap struct { tophash [bucketCnt]uint8 // Followed by bucketCnt keys and then bucketCnt values. // NOTE: packing all the keys together and then all the values together makes the // code a bit more complicated than alternating key/value/key/value/... but it allows // us to eliminate padding which would be needed for, e.g., map[int64]int8. // Followed by an overflow pointer. }
根據一個key得到value
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer
- *maptype為map的型別資訊,是編譯器在編譯期靜態生成的,裡面包含了map的一些元資訊,比如key和value的型別資訊等等
- *hmap為map的header,即map的引用
- key是一個通用的指標,代表了key的引用
- 返回值為一個指標,指向對應的value引用
hash計算找到bucket
那我們怎麼訪問到對應的bucket呢,我們需要得到對應key的hash值
alg := t.key.alg hash := alg.hash(key, uintptr(h.hash0)) m := uintptr(1)<<h.B - 1 b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
根據tophash和key定位到具體的bucket
- tophash可以快速試錯,如果tophash不相等直接跳過
- tophash相等的話,根據key的比較來判斷是否相等,如果相等則找到
- 如果當前bucket都試玩還沒有找到,則調到下一個bucket
擴容
loadFactor | %overflow | bytes/entry | hitprobe | missprobe |
---|---|---|---|---|
4.00 | 2.13 | 20.77 | 3.00 | 4.00 |
4.50 | 4.05 | 17.30 | 3.25 | 4.50 |
5.00 | 6.85 | 14.77 | 3.50 | 5.00 |
5.50 | 10.55 | 12.94 | 3.75 | 5.50 |
6.00 | 15.27 | 11.67 | 4.00 | 6.00 |
6.50 | 20.90 | 10.79 | 4.25 | 6.50 |
7.00 | 27.14 | 10.15 | 4.50 | 7.00 |
7.50 | 34.03 | 9.73 | 4.75 | 7.50 |
8.00 | 41.10 | 9.40 | 5.00 | 8.00 |
各個引數的意思:
- %overflow 溢位率,平均一個bucket有多少個kv的時候會溢位
- bytes/entry 平均存一個kv需要額外儲存多少位元組的資料
- hitprobe 找到一個存在的key平均需要找幾下
- missprobe 找到一個不存在的key平均需要找幾下
目前採用的是這一行:
| 6.50 | 20.90 | 10.79 | 4.25 | 6.50 |