Golang map 源碼解析和結構圖解 https://www.weiboke.online

歡迎訪問個人博客網站,可聊天,可寫文章https://www.weiboke.onlinegolang

簡單用例

func main() {
	a := make(map[int]int)
	a[2] = 1
	fmt.Println(a[2])
}
複製代碼

golang 使用map是至關的簡單,直接使用,無需調包。出於興趣也是出於疑問,想深刻了解map,以及map爲啥不是併發安全。上面的用例很簡單,使用map必定要make一下,才能使用,不然會由於map爲nil而panic。數組

map的容器

map的容器結構是由bmap結構數組組成,只有理解了bmap的結構,才能理解map如何CURD操做安全

如何make一個map

對於上面的例子裏,要make一個map會調用makemap_small函數,初始化一個hmap的結構bash

func makemap_small() *hmap {
	h := new(hmap)  //new 一個hmap
	h.hash0 = fastrand() //計算hash種子,用於hash時的參數 
	return h
}

// 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 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 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 } 複製代碼
  • count : map中key的個數
  • buckets:一個指針,指向bmap數組的頭指針
  • B :buckets數量的log2

如何map[k]=v

類型的不一樣,可能會調用不一樣的函數,不過原理是相似的。對於上面的用例,會調用mapassign_fast64函數併發

func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
	if h == nil {
		panic(plainError("assignment to entry in nil map"))
	}
    ...
	if h.flags&hashWriting != 0 {
		throw("concurrent map writes")
	}
	hash := t.key.alg.hash(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
	// Set hashWriting after calling alg.hash for consistency with mapassign.
	h.flags |= hashWriting

	if h.buckets == nil {
		h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
	}

again:
	bucket := hash & bucketMask(h.B)
	...
	b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))

	var insertb *bmap
	var inserti uintptr
	var insertk unsafe.Pointer

	for {
		for i := uintptr(0); i < bucketCnt; i++ {
			if b.tophash[i] == empty {
				if insertb == nil {
					insertb = b
					inserti = i
				}
				continue
			}
			k := *((*uint64)(add(unsafe.Pointer(b), dataOffset+i*8)))
			if k != key {
				continue
			}
			insertb = b
			inserti = i
			goto done
		}
		...
	}

    ...

	insertb.tophash[inserti&(bucketCnt-1)] = tophash(hash) // mask inserti to avoid bounds checks

	insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*8)
	// store new key at insert position
	*(*uint64)(insertk) = key

	h.count++

done:
	val := add(unsafe.Pointer(insertb), dataOffset+bucketCnt*8+inserti*uintptr(t.valuesize))
	if h.flags&hashWriting == 0 {
		throw("concurrent map writes")
	}
	h.flags &^= hashWriting
	return val
}
複製代碼

從上面的代碼能夠看到,若是map沒被初始化會panic,map會作簡單的併發檢查,即檢查寫標誌位(h.flags&hashWriting)是否被設置爲1,當爲1是則panic,但這種檢查並不有效,尤爲在多核處理器上,因此map不是併發安全。接着計算出key的hash值,並把寫標誌位置爲1。buckets是map的容器,用來裝值。若是buckets爲空,初始化一個對象。bucketMask計算掩碼值(若是h.B=4,則bucketMask(h.B)=1111(2);15(10)),經過hash和掩碼值相與獲得是第幾個bucket。根據bucket值,計算偏移獲取bmap指針值。開始遍歷查找是否有空的位置,存放這個key,或者是否有相同的key。若是bmap裏面沒有相同的key而且也有位置存放這個key,而後將tophash[inserti&(bucketCnt-1)]賦值爲hash值的第一個字節,主要用來標誌已存放有值。計算存放key的位置指針,賦值爲key,將hmap的count加一。最後計算存放val的位置指針,將寫標誌位置零,將指針返回,編譯器會自動添加指令,將用戶代碼中的value存到這個指針中。若是key已存在,就會直接跳到done區,也就是計算val的位置指針,將寫標誌位置零,將指針返回。app

跳過了map增加以後的代碼細節,能夠後面繼續深刻。less

如何獲取map[k]的value值

弄明白上面如何存放value的原理,取也就是至關的簡單了。函數

func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
	...
	if h == nil || h.count == 0 {
		return unsafe.Pointer(&zeroVal[0])
	}
	if h.flags&hashWriting != 0 {
		throw("concurrent map read and map write")
	}
	var b *bmap
	if h.B == 0 {
		// One-bucket table. No need to hash.
		b = (*bmap)(h.buckets)
	} else {
		hash := t.key.alg.hash(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
		m := bucketMask(h.B)
		b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
		...
	}
	for ; b != nil; b = b.overflow(t) {
		for i, k := uintptr(0), b.keys(); i < bucketCnt; i, k = i+1, add(k, 8) {
			if *(*uint64)(k) == key && b.tophash[i] != empty {
				return add(unsafe.Pointer(b), dataOffset+bucketCnt*8+i*uintptr(t.valuesize))
			}
		}
	}
	return unsafe.Pointer(&zeroVal[0])
}

複製代碼

獲取value值的過程略寫了,過程與存value值相似。首先找到bucket的位置,取得bmap的指針,而後開始遍歷查找是否存在這個key,有就返回存放value的指針值,不然返回nil。網站

相關文章
相關標籤/搜索