golang 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組成:

hashmap內部結構
// 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
 B     uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
 hash0 uint32 // hash seed

 buckets    unsafe.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
 nevacuate  uintptr        // 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內部

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值

bucket的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 |

遷移

遷移
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容