[TOC]git
籠統的來講,go的map底層是一個hash表,經過鍵值對進行映射。 鍵經過哈希函數生成哈希值,而後go底層的map數據結構就存儲相應的hash值,進行索引,最終是在底層使用的數組存儲key,和value。稍微詳細的說,就設計到go map 的結構。hmap 和bmap。github
哈希表就不得不說hash函數。hash函數,有加密型和非加密型。加密型的通常用於加密數據、數字摘要等,典型表明就是md五、sha一、sha25六、aes256這種;非加密型的通常就是查找。在map的應用場景中,用的是查找。選擇hash函數主要考察的是兩點:性能、碰撞機率。golang使用的hash算法根據硬件選擇,若是cpu支持aes,那麼使用aes hash,不然使用memhash,memhash是參考xxhash、cityhash實現的,性能很是好。golang
具體hash函數的性能比較能夠看:http://aras-p.info/blog/2016/...算法
哈希函數會將傳入的key值進行哈希運算,獲得一個惟一的值。go語言把生成的哈希值一分爲二,好比一個key通過哈希函數,生成的哈希值爲:8423452987653321
,go語言會這它拆分爲84234529
,和87653321
。那麼,前半部分就叫作高位哈希值,後半部分就叫作低位哈希值。後面會說高位哈希值和低位哈希值是作什麼用的。數組
高位哈希值:是用來肯定當前的bucket(桶)有沒有所存儲的數據的。數據結構
低位哈希值:是用來肯定,當前的數據存在了哪一個bucket(桶)函數
// Go map的一個header結構 type hmap struct { count int // map的大小. len()函數就取的這個值 flags uint8 //map狀態標識 B uint8 // 能夠最多容納 6.5 * 2 ^ B 個元素,6.5爲裝載因子即:map長度=6.5*2^B //B能夠理解爲buckets已擴容的次數 noverflow uint16 // 溢出buckets的數量 hash0 uint32 // hash 種子 buckets unsafe.Pointer //指向最大2^B個Buckets數組的指針. count==0時爲nil. oldbuckets unsafe.Pointer //指向擴容以前的buckets數組,而且容量是如今一半.不增加就爲nil nevacuate uintptr // 搬遷進度,小於nevacuate的已經搬遷 extra *mapextra // 可選字段,額外信息 }
hmap是map的最外層的一個數據結構,包括了map的各類基礎信息、如大小、bucket。首先說一下,buckets這個參數,它存儲的是指向buckets數組的一個指針,當bucket(桶爲0時)爲nil。咱們能夠理解爲,hmap指向了一個空bucket數組,而且當bucket數組須要擴容時,它會開闢一倍的內存空間,而且會漸進式的把原數組拷貝,即用到舊數組的時候就拷貝到新數組。性能
// Go map 的 buckets結構 type bmap struct { // 每一個元素hash值的高8位,若是tophash[0] < minTopHash,表示這個桶的搬遷狀態 tophash [bucketCnt]uint8 // 第二個是8個key、8個value,可是咱們不能直接看到;爲了優化對齊,go採用了key放在一塊兒,value放在一塊兒的存儲方式, // 第三個是溢出時,下一個溢出桶的地址 }
bucket(桶),每個bucket最多放8個key和value,最後由一個overflow字段指向下一個bmap,注意key、value、overflow字段都不顯示定義,而是經過maptype計算偏移獲取的。測試
bucket這三部份內容決定了它是怎麼工做的:優化
bucket的設計細節:
在golang map中出現衝突時,不是每個key都申請一個結構經過鏈表串起來,而是以bmap爲最小粒度掛載,一個bmap能夠放8個key和value。這樣減小對象數量,減輕管理內存的負擔,利於gc。
若是插入時,bmap中key超過8,那麼就會申請一個新的bmap(overflow bucket)掛在這個bmap的後面造成鏈表,優先用預分配的overflow bucket,若是預分配的用完了,那麼就malloc一個掛上去。注意golang的map不會shrink,內存只會越用越多,overflow bucket中的key全刪了也不會釋放
如圖所示:
簡單結構爲圖:
工做流程:
查找或者操做map時,首先key通過hash函數生成hash值,經過哈希值的低8位來判斷當前數據屬於哪一個桶(bucket),找到bucket之後,經過哈希值的高八位與bucket存儲的高位哈希值循環比對,若是相同就比較剛纔找到的底層數組的key值,若是key相同,取出value。若是高八位hash值在此bucket沒有,或者有,可是key不相同,就去鏈表中下一個溢出bucket中查找,直到查找到鏈表的末尾。
碰撞衝突:若是不一樣的key定位到了統一bucket或者生成了同一hash,就產生衝突。 go是經過鏈表法來解決衝突的。好比一個高八位的hash值和已經存入的hash值相同,而且此bucket存的8個鍵值對已經滿了,或者後面已經掛了好幾個bucket了。那麼這時候要存這個值就先比對key,key確定不相同啊,那就今後位置一直沿着鏈表日後找,找到一個空位置,存入它。因此這種狀況,兩個相同的hash值高8位是存在不一樣bucket中的。
查的時候也是比對hash值和key 沿着鏈表把它查出來。 還有一種狀況,就是目前就 1個bucket,而且8個key-value的數組尚未存滿,這個時候再比較完key不相同的時候,一樣是沿着當前bucket數組中的內存空間日後找,找到第一個空位,插入它。這個就至關因而用尋址法來解決衝突,查找的時候,也是先比較hash值,再比較key,而後沿着當前內存地址日後找。
go語言的map經過數組+鏈表的方式實現了hash表,同時分散各個桶,使用鏈表法+bucket內部的尋址法解決了碰撞衝突,也提升了效率。由於即便鏈表很長了,go會根據裝載因子,去擴容整個bucket數組,因此下面就要看下擴容。
這篇文章,只是對map底層的結構進行了說明,具體建立、查找、刪除等的流程是差很少,可是具體的細節仍是有不少。因此,我就不一一寫出了,貼一下其餘博主寫的文章,很詳細。
5.1 剖析golang map的實現
地址:https://www.jianshu.com/p/092...
5.2 Golang map 的底層實現
地址:https://www.jianshu.com/p/aa0...
重點來了,必須看:很是詳細的map源碼說明
https://github.com/cch123/golang-notes/blob/master/map.md
參考:
https://www.jianshu.com/p/092...