最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang 語言map底層實現(xiàn)原理解析

 更新時間:2020年12月03日 11:24:24   作者:程序員阿俊  
這篇文章主要介紹了Golang 語言map底層實現(xiàn)原理解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

在開發(fā)過程中,map是必不可少的數(shù)據(jù)結構,在Golang中,使用map或多或少會遇到與其他語言不一樣的體驗,比如訪問不存在的元素會返回其類型的空值、map的大小究竟是多少,為什么會報"cannot take the address of"錯誤,遍歷map的隨機性等等。
本文希望通過研究map的底層實現(xiàn),以解答這些疑惑。
基于Golang 1.8.3

1. 數(shù)據(jù)結構及內存管理

hashmap的定義位于 src/runtime/hashmap.go 中,首先我們看下hashmap和bucket的定義:

type hmap struct {
 count  int // 元素的個數(shù)
 flags  uint8 // 狀態(tài)標志
 B   uint8 // 可以最多容納 6.5 * 2 ^ B 個元素,6.5為裝載因子
 noverflow uint16 // 溢出的個數(shù)
 hash0  uint32 // 哈希種子
 
 buckets unsafe.Pointer // 桶的地址
 oldbuckets unsafe.Pointer // 舊桶的地址,用于擴容
 nevacuate uintptr  // 搬遷進度,小于nevacuate的已經搬遷
 overflow *[2]*[]*bmap 
}

其中,overflow是一個指針,指向一個元素個數(shù)為2的數(shù)組,數(shù)組的類型是一個指針,指向一個slice,slice的元素是桶(bmap)的地址,這些桶都是溢出桶;為什么有兩個?因為Go map在hash沖突過多時,會發(fā)生擴容操作,為了不全量搬遷數(shù)據(jù),使用了增量搬遷,[0]表示當前使用的溢出桶集合,[1]是在發(fā)生擴容時,保存了舊的溢出桶集合;overflow存在的意義在于防止溢出桶被gc。

// A bucket for a Go map.
type bmap struct {
 // 每個元素hash值的高8位,如果tophash[0] < minTopHash,表示這個桶的搬遷狀態(tài)
 tophash [bucketCnt]uint8
 // 接下來是8個key、8個value,但是我們不能直接看到;為了優(yōu)化對齊,go采用了key放在一起,value放在一起的存儲方式,
 // 再接下來是hash沖突發(fā)生時,下一個溢出桶的地址
}

tophash的存在是為了快速試錯,畢竟只有8位,比較起來會快一點。

從定義可以看出,不同于STL中map以紅黑樹實現(xiàn)的方式,Golang采用了HashTable的實現(xiàn),解決沖突采用的是鏈地址法。也就是說,使用數(shù)組+鏈表來實現(xiàn)map。特別的,對于一個key,幾個比較重要的計算公式為:

key hash hashtop bucket index
key hash := alg.hash(key, uintptr(h.hash0)) top := uint8(hash >> (sys.PtrSize*8 - 8)) bucket := hash & (uintptr(1)<<h.B - 1),即 hash % 2^B

例如,對于B = 3,當hash(key) = 4時, hashtop = 0, bucket = 4,當hash(key) = 20時,hashtop = 0, bucket = 4;這個例子我們在搬遷過程還會用到。

內存布局類似于這樣:

hashmap-buckets

2. 創(chuàng)建 - makemap

map的創(chuàng)建比較簡單,在參數(shù)校驗之后,需要找到合適的B來申請桶的內存空間,接著便是穿件hmap這個結構,以及對它的初始化。

makemap

3. 訪問 - mapaccess

對于給定的一個key,可以通過下面的操作找到它是否存在

image.png

方法定義為

// returns key, if not find, returns nil
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer 
 
// returns key and exist. if not find, returns nil, false
func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool)
 
// returns both key and value. if not find, returns nil, nil
func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer)

可見在找不到對應key的情況下,會返回nil

4. 分配 - mapassign

為一個key分配空間的邏輯,大致與查找類似;但增加了寫保護和擴容的操作;注意,分配過程和刪除過程都沒有在oldbuckets中查找,這是因為首先要進行擴容判斷和操作;如下:

assign

擴容是整個hashmap的核心算法,我們放在第6部分重點研究。

新建一個溢出桶,并將其拼接在當前桶的尾部,實現(xiàn)了類似鏈表的操作:

// 獲取當前桶的溢出桶
func (b *bmap) overflow(t *maptype) *bmap {
 return *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize))
}
 
// 設置當前桶的溢出桶
func (h *hmap) setoverflow(t *maptype, b, ovf *bmap) {
 h.incrnoverflow()
 if t.bucket.kind&kindNoPointers != 0 {
  h.createOverflow()
  //重點,這里講溢出桶append到overflow[0]的后面
  *h.overflow[0] = append(*h.overflow[0], ovf)
 }
 *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize)) = ovf
}

5. 刪除 - mapdelete

刪除某個key的操作與分配類似,由于hashmap的存儲結構是數(shù)組+鏈表,所以真正刪除key僅僅是將對應的slot設置為empty,并沒有減少內存;如下:

mapdelete

6. 擴容 - growWork

首先,判斷是否需要擴容的邏輯是

func (h *hmap) growing() bool {
 return h.oldbuckets != nil
}

何時h.oldbuckets不為nil呢?在分配assign邏輯中,當沒有位置給key使用,而且滿足測試條件(裝載因子>6.5或有太多溢出通)時,會觸發(fā)hashGrow邏輯:

func hashGrow(t *maptype, h *hmap) {
 //判斷是否需要sameSizeGrow,否則"真"擴
 bigger := uint8(1)
 if !overLoadFactor(int64(h.count), h.B) {
  bigger = 0
  h.flags |= sameSizeGrow
 }
  // 下面將buckets復制給oldbuckets
 oldbuckets := h.buckets
 newbuckets := newarray(t.bucket, 1<<(h.B+bigger))
 flags := h.flags &^ (iterator | oldIterator)
 if h.flags&iterator != 0 {
  flags |= oldIterator
 }
 // 更新hmap的變量
 h.B += bigger
 h.flags = flags
 h.oldbuckets = oldbuckets
 h.buckets = newbuckets
 h.nevacuate = 0
 h.noverflow = 0
  // 設置溢出桶
 if h.overflow != nil {
  if h.overflow[1] != nil {
   throw("overflow is not nil")
  }
// 交換溢出桶
  h.overflow[1] = h.overflow[0]
  h.overflow[0] = nil
 }
}

OK,下面正式進入重點,擴容階段;在assign和delete操作中,都會觸發(fā)擴容growWork:

func growWork(t *maptype, h *hmap, bucket uintptr) {
 // 搬遷舊桶,這樣assign和delete都直接在新桶集合中進行
 evacuate(t, h, bucket&h.oldbucketmask())
  //再搬遷一次搬遷過程中的桶
 if h.growing() {
  evacuate(t, h, h.nevacuate)
 }
}

6.1 搬遷過程

一般來說,新桶數(shù)組大小是原來的2倍(在!sameSizeGrow()條件下),新桶數(shù)組前半段可以"類比"為舊桶,對于一個key,搬遷后落入哪一個索引中呢?

 假設舊桶數(shù)組大小為2^B, 新桶數(shù)組大小為2*2^B,對于某個hash值X
若 X & (2^B) == 0,說明 X < 2^B,那么它將落入與舊桶集合相同的索引xi中;
否則,它將落入xi + 2^B中。

例如,對于舊B = 3時,hash1 = 4,hash2 = 20,其搬遷結果類似這樣。

example.png

源碼中有些變量的命名比較簡單,容易擾亂思路,我們注明一下便于理解。

變量 釋義
x *bmap 桶x表示與在舊桶時相同的位置,即位于新桶前半段
y *bmap 桶y表示與在舊桶時相同的位置+舊桶數(shù)組大小,即位于新桶后半段
xi int 桶x的slot索引
yi int 桶y的slot索引
xk unsafe.Pointer 索引xi對應的key地址
yk unsafe.Pointer 索引yi對應的key地址
xv unsafe.Pointer 索引xi對應的value地址
yv unsafe.Pointer 索引yi對應的value地址

搬遷過程如下:

evacuate

總結

到目前為止,Golang的map實現(xiàn)細節(jié)已經分析完畢,但不包含迭代器相關操作。通過分析,我們了解了map是由數(shù)組+鏈表實現(xiàn)的HashTable,其大小和B息息相關,同時也了解了map的創(chuàng)建、查詢、分配、刪除以及擴容搬遷原理??偟膩碚f,Golang通過hashtop快速試錯加快了查找過程,利用空間換時間的思想解決了擴容的問題,利用將8個key(8個value)依次放置減少了padding空間等等。

到此這篇關于Golang 語言map底層實現(xiàn)原理解析的文章就介紹到這了,更多相關Golang map底層實現(xiàn)原理內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Go語言實現(xiàn)高并發(fā)百萬QPS的關鍵技術棧

    Go語言實現(xiàn)高并發(fā)百萬QPS的關鍵技術棧

    Go語言憑借其輕量級goroutine和高效的調度器,天然適合高并發(fā)場景,Go語言通過Goroutine調度優(yōu)化、I/O多路復用等技術實現(xiàn)高并發(fā),結合性能調優(yōu)與架構設計,可支撐百萬QPS,延遲低于5ms,支持未來擴展
    2025-05-05
  • 一文帶你了解Golang中的緩沖區(qū)Buffer

    一文帶你了解Golang中的緩沖區(qū)Buffer

    作為一種常見的數(shù)據(jù)結構,緩沖區(qū)(Buffer)在計算機科學中有著廣泛的應用。這篇文章將詳細介紹?Go?中?Buffer?的用法,從多個方面介紹其特性和應用場景,需要的可以參考一下
    2023-05-05
  • Go語言中常用語法編寫與優(yōu)化技巧小結

    Go語言中常用語法編寫與優(yōu)化技巧小結

    為了充分利用?Go?的潛力,我們需要了解如何優(yōu)化?Go?程序,本文將介紹一些常見的?Go?語言優(yōu)化技巧,并通過實際例子進行說明,希望對大家有所幫助
    2024-02-02
  • 基于golang中container/list包的用法說明

    基于golang中container/list包的用法說明

    這篇文章主要介紹了基于golang中container/list包的用法說明,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 深入探究Golang中l(wèi)og標準庫的使用

    深入探究Golang中l(wèi)og標準庫的使用

    Go?語言標準庫中的?log?包設計簡潔明了,易于上手,可以輕松記錄程序運行時的信息、調試錯誤以及跟蹤代碼執(zhí)行過程中的問題等。本文主要來深入探究?log?包的使用和原理,幫助讀者更好地了解和掌握它
    2023-05-05
  • Go語言結合validator包實現(xiàn)表單驗證

    Go語言結合validator包實現(xiàn)表單驗證

    在現(xiàn)代?Web?開發(fā)中,表單驗證和錯誤處理是至關重要的環(huán)節(jié),本文將演示如何使用?Go?語言的?Gin?框架結合?validator?包,實現(xiàn)高級的表單驗證功能,需要的可以參考下
    2024-11-11
  • Go分布式鏈路追蹤實戰(zhàn)探索

    Go分布式鏈路追蹤實戰(zhàn)探索

    這篇文章主要為大家介紹了Go分布式鏈路追蹤實戰(zhàn)示例探索,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2024-01-01
  • golang語言實現(xiàn)的文件上傳與文件下載功能示例

    golang語言實現(xiàn)的文件上傳與文件下載功能示例

    這篇文章主要介紹了golang語言實現(xiàn)的文件上傳與文件下載功能,結合實例形式分析了Go語言實現(xiàn)的文件傳輸相關操作技巧,需要的朋友可以參考下
    2020-02-02
  • Go中Context使用源碼解析

    Go中Context使用源碼解析

    這篇文章主要為大家介紹了Go中Context使用源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • go開發(fā)中引用靜態(tài)庫.a文件的方法

    go開發(fā)中引用靜態(tài)庫.a文件的方法

    這篇文章主要介紹了go開發(fā)中引用靜態(tài)庫.a文件的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-11-11

最新評論

北安市| 义马市| 上饶县| 石城县| 都江堰市| 疏勒县| 西丰县| 昌黎县| 托里县| 城固县| 清远市| 崇仁县| 航空| 元阳县| 清新县| 东乌珠穆沁旗| 平遥县| 平武县| 浮梁县| 宣化县| 高邮市| 榆中县| 荣成市| 杂多县| 尖扎县| 乐平市| 沐川县| 禹城市| 雅安市| 长阳| 长宁区| 淅川县| 来宾市| 凤翔县| 盱眙县| 邵阳市| 旺苍县| 蓝田县| 庄河市| 繁峙县| 滁州市|