golang map底層原理

映射是一個(gè)集合,可以使用類似處理數(shù)組和切片的方式迭代映射中的元素。但映射是無序的集合萎庭,意味著沒有辦法預(yù)測鍵值對(duì)被返回的順序。即便使用同樣的順序保存鍵值對(duì)齿拂,每次迭代映射的時(shí)候順序也可能不一樣驳规。無序的原因是映射的實(shí)現(xiàn)使用了散列表。go語言中的map采用的是哈希查找表署海,由一個(gè)key通過哈希函數(shù)得到哈希值吗购,64位系統(tǒng)中就生成一個(gè)64bit的哈希值,由這個(gè)哈希值將key對(duì)應(yīng)到不同的桶(bucket)中砸狞,當(dāng)有多個(gè)哈希映射到相同的的桶中時(shí)捻勉,使用鏈表解決哈希沖突。
hash函數(shù)
golang中的map使用hash查找刀森,就是將key做hash運(yùn)算得到一個(gè)哈希值踱启,根據(jù)哈希值確定key-value落在哪個(gè)bucket的哪個(gè)cell。hash算法和CPU有關(guān),如果cpu支持aes埠偿,那么使用aes hash透罢,否則使用memhash。
數(shù)據(jù)結(jié)構(gòu)

// A header for a Go map.
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    flags     uint8  //map狀態(tài)標(biāo)識(shí),比如是否在被寫或遷移等
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    hash0     uint32 // hash seed隨機(jī)hash種子

    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)

    extra *mapextra // optional fields
}
//用**mapextra**來存儲(chǔ)key和value都不是指針類型的map冠蒋,并且大小都小于128字節(jié)羽圃,這樣可以避免GC掃描整個(gè)map
// mapextra holds fields that are not present on all maps.
type mapextra struct {
    // If both key and elem 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.extra.overflow and hmap.extra.oldoverflow.
    // overflow and oldoverflow are only used if key and elem do not contain pointers.
    // overflow contains overflow buckets for hmap.buckets.
    // oldoverflow contains overflow buckets for hmap.oldbuckets.
    // The indirection allows to store a pointer to the slice in hiter.
    overflow    *[]*bmap
    oldoverflow *[]*bmap

    // nextOverflow holds a pointer to a free overflow bucket.
    nextOverflow *bmap
}
// A bucket for a Go map.
//**bmap**可以理解為buckets of map的縮寫,它就是map中bucket的本體抖剿,即存key和value數(shù)據(jù)的“桶”
type bmap struct {
    // tophash generally contains the top byte of the hash value
    // for each key in this bucket. If tophash[0] < minTopHash,
    // tophash[0] is a bucket evacuation state instead.
    tophash [bucketCnt]uint8
    // Followed by bucketCnt keys and then bucketCnt elems.
    // NOTE: packing all the keys together and then all the elems together makes the
    // code a bit more complicated than alternating key/elem/key/elem/... but it allows
    // us to eliminate padding which would be needed for, e.g., map[int64]int8.
    // Followed by an overflow pointer.
}

根據(jù)哈希函數(shù)將key生成一個(gè)hash值朽寞,其中低位hash用來判斷桶的位置,高位hash確定在桶中的哪個(gè)cell斩郎。低位哈希就是哈希值的低B位脑融,hmap結(jié)構(gòu)體中的B,比如B為5孽拷,2^5=32吨掌,即該map有32個(gè)桶,只需要取哈希值的低5位就可以確定當(dāng)前key-value落在哪個(gè)桶(bucket)中脓恕;高位哈希即tophash,是指哈希值的高8bits窿侈,根據(jù)tophash來確定key在桶中的位置炼幔。每個(gè)桶可以存儲(chǔ)8對(duì)key-value,存儲(chǔ)結(jié)構(gòu)不是key/value/key/value...史简,而是key/key..value/value乃秀,這樣可以避免字節(jié)對(duì)齊時(shí)的padding,節(jié)省內(nèi)存空間圆兵。
當(dāng)不同的key根據(jù)哈希得到的tophash和低位hash都一樣跺讯,發(fā)生哈希碰撞,這個(gè)時(shí)候就體現(xiàn)overflow pointer字段的作用了殉农。桶溢出時(shí)刀脏,就需要把key-value對(duì)存儲(chǔ)在overflow bucket(溢出桶),overflow pointer就是指向overflow bucket的指針超凳。如果overflow bucket也溢出了呢愈污?那就再給overflow bucket新建一個(gè)overflow bucket,用指針串起來就形成了鏈?zhǔn)浇Y(jié)構(gòu)轮傍,map本身有2^B個(gè)bucket暂雹,只有當(dāng)發(fā)生哈希碰撞后才會(huì)在bucket后鏈?zhǔn)皆黾觨verflow bucket。
map內(nèi)存布局
擴(kuò)容

裝填因子是否大于6.5
裝填因子 = 元素個(gè)數(shù)/桶個(gè)數(shù)创夜,大于6.5時(shí)杭跪,說明桶快要裝滿,需要擴(kuò)容

overflow bucket是否太多
當(dāng)bucket的數(shù)量 < 2^15,但overflow bucket的數(shù)量大于桶數(shù)量
當(dāng)bucket的數(shù)量 >= 2^15涧尿,但overflow bucket的數(shù)量大于2^15

雙倍擴(kuò)容:裝載因子多大系奉,直接翻倍,B+1现斋;擴(kuò)容也不是申請一塊內(nèi)存喜最,立馬開始拷貝,每一次訪問舊的buckets時(shí)庄蹋,就遷移一部分瞬内,直到完成,舊bucket被GC回收限书。

等量擴(kuò)容:重新排列虫蝶,極端情況下,重新排列也解決不了倦西,map成了鏈表能真,性能大大降低,此時(shí)哈希種子hash0的設(shè)置扰柠,可以降低此類極端場景的發(fā)生粉铐。
查找
根據(jù)key計(jì)算出哈希值
根據(jù)哈希值低位確定所在bucket
根據(jù)哈希值高8位確定在bucket中的存儲(chǔ)位置
當(dāng)前bucket未找到則查找對(duì)應(yīng)的overflow bucket。
對(duì)應(yīng)位置有數(shù)據(jù)則對(duì)比完整的哈希值卤档,確定是否是要查找的數(shù)據(jù)
如果當(dāng)前處于map進(jìn)行了擴(kuò)容蝙泼,處于數(shù)據(jù)搬移狀態(tài),則優(yōu)先從oldbuckets查找劝枣。
插入
根據(jù)key計(jì)算出哈希值
根據(jù)哈希值低位確定所在bucket
根據(jù)哈希值高8位確定在bucket中的存儲(chǔ)位置
查找該key是否存在汤踏,已存在則更新,不存在則插入
map無序
map的本質(zhì)是散列表舔腾,而map的增長擴(kuò)容會(huì)導(dǎo)致重新進(jìn)行散列溪胶,這就可能使map的遍歷結(jié)果在擴(kuò)容前后變得不可靠,Go設(shè)計(jì)者為了讓大家不依賴遍歷的順序稳诚,故意在實(shí)現(xiàn)map遍歷時(shí)加入了隨機(jī)數(shù)哗脖,讓每次遍歷的起點(diǎn)--即起始bucket的位置不一樣,即不讓遍歷都從bucket0開始采桃,所以即使未擴(kuò)容時(shí)我們遍歷出來的map也總是無序的懒熙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市普办,隨后出現(xiàn)的幾起案子工扎,更是在濱河造成了極大的恐慌,老刑警劉巖衔蹲,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肢娘,死亡現(xiàn)場離奇詭異呈础,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橱健,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門而钞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人拘荡,你說我怎么就攤上這事臼节。” “怎么了珊皿?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵网缝,是天一觀的道長。 經(jīng)常有香客問我蟋定,道長粉臊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任驶兜,我火速辦了婚禮扼仲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抄淑。我一直安慰自己屠凶,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布肆资。 她就那樣靜靜地躺著阅畴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪迅耘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天监署,我揣著相機(jī)與錄音颤专,去河邊找鬼。 笑死钠乏,一個(gè)胖子當(dāng)著我的面吹牛栖秕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晓避,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼簇捍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俏拱?” 一聲冷哼從身側(cè)響起暑塑,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锅必,沒想到半個(gè)月后事格,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年驹愚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了远搪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡逢捺,死狀恐怖谁鳍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劫瞳,我是刑警寧澤倘潜,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站柠新,受9級(jí)特大地震影響窍荧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恨憎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一蕊退、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧憔恳,春花似錦瓤荔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至程梦,卻和暖如春点把,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屿附。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國打工郎逃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挺份。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓褒翰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匀泊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子优训,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359