golang hashmap的使用及實(shí)現(xiàn)

基本語法

定義hashmap變量

由于go語言是一個(gè)強(qiáng)類型的語言,因此hashmap也是有類型的,具體體現(xiàn)在key和value都必須指定類型,比如聲明一個(gè)key為string,value也是string的map,
需要這樣做

var m map[string]string // 聲明一個(gè)hashmap,還不能直接使用,必須使用make來初始化
m = make(map[string]string) // 初始化一個(gè)map
m = make(map[string]string, 3) // 初始化一個(gè)map并附帶一個(gè)可選的初始bucket(非準(zhǔn)確值,只是有提示意義)

m := map[string]string{} // 聲明并初始化

m := make(map[string]string) // 使用make來初始化

大部分類型都能做key,某些類型是不能的,共同的特點(diǎn)是:不能使用==來比較,包括: slice, map, function

get,set,delete

m := map[string]int
m["a"] = 1

fmt.Println(m["a"]) // 輸出 1

// 如果訪問一個(gè)不存在的key,返回類型默認(rèn)值
fmt.Println(m["b"]) // 輸出0

// 測(cè)試key是否存在
v, ok := m["b"]
if ok {
    ...
}

// 刪除一個(gè)key
delete(m, "a")

迭代器

// 只迭代key
for k := range m {
    ...
}

// 同時(shí)迭代key-value
for k, v := range m {
    ...
}

在迭代的過程中是可以對(duì)map進(jìn)行刪除和更新操作的,規(guī)則如下:

  • 迭代是無序的,跟插入是的順序無關(guān)
  • 迭代的過程中刪除一個(gè)key,無論遍歷還是沒有遍歷過都不會(huì)再遍歷到
  • 迭代的過程中添加一個(gè)key,不確定是否能遍歷到
  • 未初始化的map也可以迭代

其他

  • map的value是不可取地址的,意味著 &m["a"]這樣的語法是非法的
  • len可以獲取當(dāng)前map的kv個(gè)數(shù)

內(nèi)部結(jié)構(gòu)

hashmap結(jié)構(gòu)

golang的map是hash結(jié)構(gòu)的,意味著平均訪問時(shí)間是O(1)的。同傳統(tǒng)的hashmap一樣,由一個(gè)個(gè)bucket組成:

hashmap內(nèi)部結(jié)構(gòu)
// 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內(nèi)部

bucket內(nèi)部
// 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.
}

根據(jù)一個(gè)key得到value

func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer
  • *maptype為map的類型信息,是編譯器在編譯期靜態(tài)生成的,里面包含了map的一些元信息,比如key和value的類型信息等等
  • *hmap為map的header,即map的引用
  • key是一個(gè)通用的指針,代表了key的引用
  • 返回值為一個(gè)指針,指向?qū)?yīng)的value引用

hash計(jì)算找到bucket

那我們?cè)趺丛L問到對(duì)應(yīng)的bucket呢,我們需要得到對(duì)應(yīng)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)))

根據(jù)tophash和key定位到具體的bucket

  • tophash可以快速試錯(cuò),如果tophash不相等直接跳過
  • tophash相等的話,根據(jù)key的比較來判斷是否相等,如果相等則找到
  • 如果當(dāng)前bucket都試玩還沒有找到,則調(diào)到下一個(gè)bucket

擴(kuò)容

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

各個(gè)參數(shù)的意思:

  • %overflow 溢出率,平均一個(gè)bucket有多少個(gè)kv的時(shí)候會(huì)溢出
  • bytes/entry 平均存一個(gè)kv需要額外存儲(chǔ)多少字節(jié)的數(shù)據(jù)
  • hitprobe 找到一個(gè)存在的key平均需要找?guī)紫?/li>
  • missprobe 找到一個(gè)不存在的key平均需要找?guī)紫?/li>

目前采用的是這一行:

| 6.50 | 20.90 | 10.79 | 4.25 | 6.50 |

遷移

遷移
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容