package main
import (
"fmt"
)
func main(){
mapa:= make(map[string]int, 10)
// var mapa map[string]int
mapa["zhao"] = 1
mapa["qian"] = 2
fmt.Println(mapa["li"])
}
复制代码
看上面的例子,咱们可能存在的疑问有如下几个:git
等等,或许你还有其余的疑问,咱们首先看看上面的几个疑问吧github
先看看在make建立map时,参数为10.具体执行的操做golang
0x0050 00080 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $1
0x0050 00080 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ type.map[string]int(SB), AX
0x0057 00087 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x0057 00087 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, (SP)
0x005b 00091 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ $10, 8(SP)
0x0064 00100 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $1
0x0064 00100 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $0
0x0064 00100 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_2+136(SP), AX
0x006c 00108 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x006c 00108 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, 16(SP)
0x0071 00113 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) CALL runtime.makemap(SB)
0x0076 00118 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $1
0x0076 00118 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ 24(SP), AX
0x007b 00123 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $2
0x007b 00123 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, "".mapa+56(SP)
复制代码
执行的makemap
调用,咱们看看这个函数的参数和返回值状况,第一个参数是type.map[string]int(SB)
,第二个参数是10
,第三个参数颇有意思数组
0x0064 00100 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $0
0x0064 00100 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_2+136(SP), AX
0x006c 00108 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x006c 00108 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, 16(SP)
复制代码
暂且不论它是啥,先看看makemap函数的原型bash
// makemap 实现了Go map的建立,经过make(map[k]v, hint).
// 若是编译器肯定该映射或第一个存储桶,能够在堆栈上建立,h和/或bucket能够为非nil。
// 若是参数h 不为nil,map能够直接在h中建立。
// 若是h.buckets 不为nil,bucket指针指向的能够用于做为第一个桶。
func makemap(t *maptype, hint int, h *hmap) *hmap {
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
// initialize Hmap
if h == nil {
h = new(hmap)
}
h.hash0 = fastrand()
// Find the size parameter B which will hold the requested # of elements.
// For hint < 0 overLoadFactor returns false since hint < bucketCnt.
B := uint8(0)
for overLoadFactor(hint, B) {
B++
}
h.B = B
// allocate initial hash table
// if B == 0, the buckets field is allocated lazily later (in mapassign)
// If hint is large zeroing this memory could take a while.
if h.B != 0 {
var nextOverflow *bmap
h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
if nextOverflow != nil {
h.extra = new(mapextra)
h.extra.nextOverflow = nextOverflow
}
}
return h
}
复制代码
从原型中看,第三个参数是hmap指针,这个东西是啥呢,若是h为nil,还须要new一个?数据结构
const (
// 一个桶中能够容纳的最大数据的key/value对
bucketCntBits = 3
bucketCnt = 1 << bucketCntBits
...
)
// A header for a Go map.
type hmap struct {
count int // 元素个数,必须位于第一项,调用 len(map) 时,直接返回此值
flags uint8
B uint8 // B 是 buckets 数组的长度以2为底的对数, (能够容纳 loadFactor * 2^B 个元素)
noverflow uint16 // 溢出桶的大概数量
hash0 uint32 // 哈希种子
buckets unsafe.Pointer // 数据存储桶
oldbuckets unsafe.Pointer // 扩容前的桶,只有在扩容的时候非空。
nevacuate uintptr // progress counter for evacuation (buckets less than this have been evacuated)
extra *mapextra // 可选字段
}
// 桶的数据结构原型结构体.
type bmap struct {
// tophash 一般包含桶中每一个键的hash值的最高字节(8位)
tophash [bucketCnt]uint8 // 8字节长度的数组
// 以后是bucketCnt数量的键,而后是bucketCnt数量的值。
// 后跟一个溢出指针。
}
复制代码
overLoadFactor函数的原型为app
const (
...
// 触发增加的存储桶的最大平均负载为6.5。
// 表示为loadFactorNum / loadFactDen,以容许整数数学运算。
loadFactorNum = 13
loadFactorDen = 2
...
)
// bucketShift返回1 << b,已针对代码生成进行了优化。
func bucketShift(b uint8) uintptr {
if sys.GoarchAmd64|sys.GoarchAmd64p32|sys.Goarch386 != 0 {
b &= sys.PtrSize*8 - 1 // help x86 archs remove shift overflow checks
}
return uintptr(1) << b
}
// overLoadFactor报告count个项目放在1<<B的桶中,是否超过负载因子
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
复制代码
首先判断count是否大于bucketCnt,上述例子中,count就是hint,就是传入的参数10
.bucketCnt
是单个桶能容纳的最大键值对,为8
.条件成立后,继续判断count是否大于6.5*(1<<b)
若是大于返回true
不然返回false
less
在makemap
函数中,在对h的B值进行赋值以前,进行了一个检测overLoadFactor
循环,最终肯定下来的B值为1.而B是 buckets 数组的长度以2为底的对数, (能够容纳 loadFactor * 2^B 个元素).而B不为0,会进行桶数组的分配操做函数
// makeBucketArray初始化map存储区的底层数组。
// 1<<b是要分配桶的最小数据
// dirtyalloc 应该是nil或者是一个以前使用的t和b参数已经分配的桶数组
// 若是dirtyalloc是nil 表示一个新的桶数组将会分配,
// 不然dirtyalloc将被清空并做为桶数组重用
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
base := bucketShift(b)
nbuckets := base
// 对于小b,溢出桶不太可能出现。避免计算的开销。
if b >= 4 {
// Add on the estimated number of overflow buckets
// required to insert the median number of elements
// used with this value of b.
nbuckets += bucketShift(b - 4)
sz := t.bucket.size * nbuckets
up := roundupsize(sz)
if up != sz {
nbuckets = up / t.bucket.size
}
}
if dirtyalloc == nil {
buckets = newarray(t.bucket, int(nbuckets))
} else {
// dirtyalloc was previously generated by
// the above newarray(t.bucket, int(nbuckets))
// but may not be empty.
buckets = dirtyalloc
size := t.bucket.size * nbuckets
if t.bucket.kind&kindNoPointers == 0 {
memclrHasPointers(buckets, size)
} else {
memclrNoHeapPointers(buckets, size)
}
}
if base != nbuckets {
// We preallocated some overflow buckets.
// To keep the overhead of tracking these overflow buckets to a minimum,
// we use the convention that if a preallocated overflow bucket's overflow // pointer is nil, then there are more available by bumping the pointer. // We need a safe non-nil pointer for the last overflow bucket; just use buckets. nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize))) last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize))) last.setoverflow(t, (*bmap)(buckets)) } return buckets, nextOverflow } 复制代码
初次调用的时候,dirtyalloc参数是nil,会首次建立一个数组oop
if dirtyalloc == nil {
buckets = newarray(t.bucket, int(nbuckets))
}
复制代码
后续扩容的时候,其所指的地址为旧桶的地址,回到咱们以前的问题,mapa:= make(map[string]int, 10)
type hmap struct {
count int // 0
flags uint8
B uint8 // 1
noverflow uint16 // 溢出桶的大概数量
hash0 uint32 // 哈希种子
buckets unsafe.Pointer // 数据存储桶
oldbuckets unsafe.Pointer // 扩容前的桶,只有在扩容的时候非空。
nevacuate uintptr
extra *mapextra // 可选字段
}
复制代码
再来看看第一个问题,这个参数10,是用来在分配底层桶数组时,分配几个桶数组,为10时,B的值为1,分配两个桶数组,每一个桶数组8个键值对,足够使用了,若是为20,则B的值为2,底层会分配4个桶数组。若是参数为5时,B的值为1,分配两个桶...
固然了若是在make()建立map的时候,不提供第二个参数也是能够的,可是你会发现,在提供的参数小于bucketCnt时,汇编后是看不到makemap的函数调用的。
0x0032 00050 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $1
0x0032 00050 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) XORPS X1, X1
0x0035 00053 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVUPS X1, ""..autotmp_3+136(SP)
0x003d 00061 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) XORPS X1, X1
0x0040 00064 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVUPS X1, ""..autotmp_3+152(SP)
0x0048 00072 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) XORPS X1, X1
0x004b 00075 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVUPS X1, ""..autotmp_3+168(SP)
0x0053 00083 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $1
0x0053 00083 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $2
0x0053 00083 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_4+184(SP), DI
0x005b 00091 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) XORPS X0, X0
0x005e 00094 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x005e 00094 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ -48(DI), DI
0x0062 00098 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) DUFFZERO $239
0x0075 00117 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $2
0x0075 00117 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_3+136(SP), AX
0x007d 00125 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x007d 00125 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) TESTB AL, (AX)
0x007f 00127 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $2
0x007f 00127 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $1
0x007f 00127 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_4+184(SP), AX
0x0087 00135 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x0087 00135 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, ""..autotmp_3+152(SP)
0x008f 00143 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $2
0x008f 00143 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_3+136(SP), AX
0x0097 00151 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x0097 00151 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $3
0x0097 00151 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, ""..autotmp_5+88(SP)
0x009c 00156 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) CALL runtime.fastrand(SB)
0x00a1 00161 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $2
0x00a1 00161 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $1
0x00a1 00161 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ ""..autotmp_5+88(SP), AX
0x00a6 00166 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) TESTB AL, (AX)
0x00a8 00168 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVL (SP), CX
0x00ab 00171 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x00ab 00171 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVL CX, 12(AX)
0x00ae 00174 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $2
0x00ae 00174 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $0
0x00ae 00174 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) LEAQ ""..autotmp_3+136(SP), AX
0x00b6 00182 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $2, $0
0x00b6 00182 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) PCDATA $0, $4
0x00b6 00182 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:8) MOVQ AX, "".mapa+56(SP)
复制代码
能够看到,直到进行mapa赋值操做,都没有看到makemap的函数调用。惟一的调用可能就是fastrand
. 编译器肯定 make map 函数的位置在:cmd/compile/internal/gc/walk.go:1199(go version go1.12.1 darwin/amd64):
case OMAKEMAP:
t := n.Type
hmapType := hmap(t)
hint := n.Left
// var h *hmap
var h *Node
if n.Esc == EscNone {
// Allocate hmap on stack.
// var hv hmap
hv := temp(hmapType)
zero := nod(OAS, hv, nil)
zero = typecheck(zero, ctxStmt)
init.Append(zero)
// h = &hv
h = nod(OADDR, hv, nil)
// Allocate one bucket pointed to by hmap.buckets on stack if hint
// is not larger than BUCKETSIZE. In case hint is larger than
// BUCKETSIZE runtime.makemap will allocate the buckets on the heap.
// Maximum key and value size is 128 bytes, larger objects
// are stored with an indirection. So max bucket size is 2048+eps.
if !Isconst(hint, CTINT) ||
hint.Val().U.(*Mpint).CmpInt64(BUCKETSIZE) <= 0 {
// var bv bmap
bv := temp(bmap(t))
zero = nod(OAS, bv, nil)
zero = typecheck(zero, ctxStmt)
init.Append(zero)
// b = &bv
b := nod(OADDR, bv, nil)
// h.buckets = b
bsym := hmapType.Field(5).Sym // hmap.buckets see reflect.go:hmap
na := nod(OAS, nodSym(ODOT, h, bsym), b)
na = typecheck(na, ctxStmt)
init.Append(na)
}
}
if Isconst(hint, CTINT) && hint.Val().U.(*Mpint).CmpInt64(BUCKETSIZE) <= 0 {
// Handling make(map[any]any) and
// make(map[any]any, hint) where hint <= BUCKETSIZE
// special allows for faster map initialization and
// improves binary size by using calls with fewer arguments.
// For hint <= BUCKETSIZE overLoadFactor(hint, 0) is false
// and no buckets will be allocated by makemap. Therefore,
// no buckets need to be allocated in this code path.
if n.Esc == EscNone {
// Only need to initialize h.hash0 since
// hmap h has been allocated on the stack already.
// h.hash0 = fastrand()
rand := mkcall("fastrand", types.Types[TUINT32], init)
hashsym := hmapType.Field(4).Sym // hmap.hash0 see reflect.go:hmap
a := nod(OAS, nodSym(ODOT, h, hashsym), rand)
a = typecheck(a, ctxStmt)
a = walkexpr(a, init)
init.Append(a)
n = convnop(h, t)
} else {
// Call runtime.makehmap to allocate an
// hmap on the heap and initialize hmap's hash0 field. fn := syslook("makemap_small") fn = substArgTypes(fn, t.Key(), t.Elem()) n = mkcall1(fn, n.Type, init) } } else { if n.Esc != EscNone { h = nodnil() } // Map initialization with a variable or large hint is // more complicated. We therefore generate a call to // runtime.makemap to intialize hmap and allocate the // map buckets. // When hint fits into int, use makemap instead of // makemap64, which is faster and shorter on 32 bit platforms. fnname := "makemap64" argtype := types.Types[TINT64] // Type checking guarantees that TIDEAL hint is positive and fits in an int. // See checkmake call in TMAP case of OMAKE case in OpSwitch in typecheck1 function. // The case of hint overflow when converting TUINT or TUINTPTR to TINT // will be handled by the negative range checks in makemap during runtime. if hint.Type.IsKind(TIDEAL) || maxintval[hint.Type.Etype].Cmp(maxintval[TUINT]) <= 0 { fnname = "makemap" argtype = types.Types[TINT] } fn := syslook(fnname) fn = substArgTypes(fn, hmapType, t.Key(), t.Elem()) n = mkcall1(fn, n.Type, init, typename(n.Type), conv(hint, argtype), h) } 复制代码
再来看第二个问题,若是使用var 进行变量声明以后,是否能够直接键值对赋值呢,slice进行声明以后就行啊,是否是map也能够?
var mapa map[string]int
mapa["zhao"] = 1
复制代码
运行报错:
panic: assignment to entry in nil map
goroutine 1 [running]:
main.main()
/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11 +0x4f
exit status 2
复制代码
但从错误信息上,咱们能够得知,在nil数组上,进行分配操做。
0x002f 00047 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:9) MOVQ $0, "".mapa+56(SP)
复制代码
var声明的map变量是一个nil map.
0x002f 00047 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:9) PCDATA $0, $1
0x002f 00047 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:9) MOVQ $0, "".mapa+56(SP)
0x0038 00056 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) PCDATA $2, $1
0x0038 00056 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) LEAQ type.map[string]int(SB), AX
0x003f 00063 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) PCDATA $2, $0
0x003f 00063 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ AX, (SP)
0x0043 00067 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ $0, 8(SP)
0x004c 00076 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) PCDATA $2, $1
0x004c 00076 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) LEAQ go.string."zhao"(SB), AX
0x0053 00083 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) PCDATA $2, $0
0x0053 00083 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ AX, 16(SP)
0x0058 00088 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ $4, 24(SP)
0x0061 00097 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) CALL runtime.mapassign_faststr(SB)
复制代码
在进行键值对赋值的操做上,调用的是CALL runtime.mapassign_faststr(SB)
func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {
if h == nil {
panic(plainError("assignment to entry in nil map"))
}
if raceenabled {
callerpc := getcallerpc()
racewritepc(unsafe.Pointer(h), callerpc, funcPC(mapassign_faststr))
}
if h.flags&hashWriting != 0 {
throw("concurrent map writes")
}
// 计算key的hash值
key := stringStructOf(&s)
hash := t.key.alg.hash(noescape(unsafe.Pointer(&s)), uintptr(h.hash0))
// Set hashWriting after calling alg.hash for consistency with mapassign.
h.flags ^= hashWriting
// 若是桶数组为空,分配长度为1的桶数组。
if h.buckets == nil {
h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
}
again:
// 计算低 B 位 hash,根据计算出的 bucketMask 选择对应的 bucket
// bucketMask returns 1<<h.B - 1
bucket := hash & bucketMask(h.B)
if h.growing() {
growWork_faststr(t, h, bucket)
}
// 计算出存储的 bucket 的内存位置
// pos = start + bucketNumber * bucetsize
b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
// 计算高 8 位 hash
top := tophash(hash)
var insertb *bmap
var inserti uintptr
var insertk unsafe.Pointer
bucketloop:
for {
for i := uintptr(0); i < bucketCnt; i++ {
if b.tophash[i] != top {
if isEmpty(b.tophash[i]) && insertb == nil {
insertb = b
inserti = i
}
if b.tophash[i] == emptyRest {
break bucketloop
}
continue
}
k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+i*2*sys.PtrSize))
if k.len != key.len {
continue
}
if k.str != key.str && !memequal(k.str, key.str, uintptr(key.len)) {
continue
}
// already have a mapping for key. Update it.
inserti = i
insertb = b
goto done
}
ovf := b.overflow(t)
if ovf == nil {
break
}
b = ovf
}
// Did not find mapping for key. Allocate new cell & add entry.
// If we hit the max load factor or we have too many overflow buckets,
// and we're not already in the middle of growing, start growing. if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) { hashGrow(t, h) goto again // Growing the table invalidates everything, so try again } if insertb == nil { // all current buckets are full, allocate a new one. insertb = h.newoverflow(t, b) inserti = 0 // not necessary, but avoids needlessly spilling inserti } insertb.tophash[inserti&(bucketCnt-1)] = top // mask inserti to avoid bounds checks insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*2*sys.PtrSize) // store new key at insert position *((*stringStruct)(insertk)) = *key h.count++ done: val := add(unsafe.Pointer(insertb), dataOffset+bucketCnt*2*sys.PtrSize+inserti*uintptr(t.valuesize)) if h.flags&hashWriting == 0 { throw("concurrent map writes") } h.flags &^= hashWriting return val } 复制代码
该函数首先判断的就是参数h的值是否为nil.也就是第二个参数,
0x0043 00067 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ $0, 8(SP)
复制代码
第三个问题,获取不到的key,如何处理
0x00c8 00200 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $0, $0
0x00c8 00200 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ "".mapa+56(SP), AX
0x00cd 00205 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $0
0x00cd 00205 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ AX, 8(SP)
0x00d2 00210 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $1
0x00d2 00210 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) LEAQ go.string."li"(SB), AX
0x00d9 00217 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $0
0x00d9 00217 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ AX, 16(SP)
0x00de 00222 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ $2, 24(SP)
0x00e7 00231 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) CALL runtime.mapaccess1_faststr(SB)
0x00ec 00236 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $1
0x00ec 00236 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ 32(SP), AX
复制代码
获取键值,调用mapaccess1_faststr
函数,该函数第一个参数是type.map[string]int(SB)
,第二个参数就是咱们的map变量.
0x00c8 00200 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $0, $0
0x00c8 00200 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ "".mapa+56(SP), AX
0x00cd 00205 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $0
0x00cd 00205 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ AX, 8(SP)
复制代码
第三个参数是key的string。
0x00d2 00210 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $1
0x00d2 00210 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) LEAQ go.string."li"(SB), AX
0x00d9 00217 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) PCDATA $2, $0
0x00d9 00217 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ AX, 16(SP)
0x00de 00222 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) MOVQ $2, 24(SP)
0x00e7 00231 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:26) CALL runtime.mapaccess1_faststr(SB)
复制代码
调用mapaccess1_faststr
,该函数首先在桶数组中检查每一个key,当前桶检查完毕后,继续溢出桶的检查,若是发现都没有这个key ,则返回一个unsafe.Pointer
的零值。
return unsafe.Pointer(&zeroVal[0])
复制代码
若是发现了参数key,会返回该参数key对应的value地址。
0x00f6 00246 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) PCDATA $2, $0
0x00f6 00246 (/Users/zhaojunwei/workspace/src/just.for.test/maptest/demo.go:11) MOVQ $1, (AX)
复制代码
而后把赋的值放到该寄存器中。
放一张曹大的一张关于map的全瞰图,更加明白map的底层结构
┌─────────────┐
│ hmap │
├─────────────┴──────────────────┐ ┌───────────────┐ ┌─────────┐ ┌─────────┐
│ count int │ │ │ ┌─────────────────▶│ bmap │ ┌───▶│ bmap │
│ │ │ ▼ │ ├─────────┴─────────────────────┐ │ ├─────────┴─────────────────────┐
├────────────────────────────────┤ │ ────────┬─────┐ │ │ tophash [bucketCnt]uint8 │ │ │ tophash [bucketCnt]uint8 │
│ flags uint8 │ │ ▲ │ 0 │ │ │ │ │ │ │
│ │ │ │ │ │──────────────────┘ ├──────────┬────────────────────┤ │ ├──────────┬────────────────────┤
├────────────────────────────────┤ │ │ ├─────┤ │ keys │ │ │ │ keys │ │
│ B uint8 │ │ │ │ 1 │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
│ │ │ │ │ │──────────────────┐ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
├────────────────────────────────┤ │ │ ├─────┤ │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤ │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤
│ noverflow uint16 │ │ │ │ 2 │ │ │ values │ │ │ │ values │ │
│ │ │ │ │ │ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
├────────────────────────────────┤ │ │ ├─────┤ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
│ hash0 uint32 │ │ │ │ 3 │ │ ├───┴───┴───┴───┴───┴───┴───┴───┤ │ ├───┴───┴───┴───┴───┴───┴───┴───┤
│ │ │ │ │ │ │ │ overflow *bmap │ │ │ overflow *bmap │
├────────────────────────────────┤ │ │ ├─────┤ │ │ │────┘ │ │
│ buckets unsafe.Pointer │ │ │ │ 4 │ │ ├─────────┬─────────────────────┘ └───────────────────────────────┘
│ │───────────┘ │ │ │ └─────────────────▶│ bmap │
├────────────────────────────────┤ ├─────┤ ├─────────┴─────────────────────┐
│ oldbuckets unsafe.Pointer │ │ 5 │ │ tophash [bucketCnt]uint8 │
│ │ │ │ │ │
├────────────────────────────────┤ size = 2 ^ B ├─────┤ ├──────────┬────────────────────┤
│ nevacuate uintptr │ │ 6 │ │ keys │ │
│ │ │ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
├────────────────────────────────┤ │ ├─────┤ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
│ extra *mapextra │ │ │ 7 │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤
┌──│ │ │ │ │ │ values │ │
│ └────────────────────────────────┘ │ └─────┘ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
│ │ .... │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
│ │ ├───┴───┴───┴───┴───┴───┴───┴───┤
│ │ ┌─────┐ │ overflow *bmap │
│ │ │ 61 │ │ │
│ │ │ │ └───────────────────────────────┘
▼ │ ├─────┤ ............
┌─────────────┐ │ │ 62 │ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ mapextra │ │ │ │────────────────────────────────────▶│ bmap │ ┌───▶│ bmap │ ┌───▶│ bmap │
├─────────────┴──────────────┐ │ ├─────┤ ├─────────┴─────────────────────┐ │ ├─────────┴─────────────────────┐ │ ├─────────┴─────────────────────┐
│ overflow *[]*bmap │ │ │ 63 │ │ tophash [bucketCnt]uint8 │ │ │ tophash [bucketCnt]uint8 │ │ │ tophash [bucketCnt]uint8 │
│ │ ▼ │ │──────────────────┐ │ │ │ │ │ │ │ │
├────────────────────────────┤ ────────┴─────┘ │ ├──────────┬────────────────────┤ │ ├──────────┬────────────────────┤ │ ├──────────┬────────────────────┤
│ oldoverflow *[]*bmap │ │ │ keys │ │ │ │ keys │ │ │ │ keys │ │
│ │ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
├────────────────────────────┤ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
│ nextoverflow *bmap │ │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤ │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤ │ ├───┴───┴──┬┴───┴───┴───┴───┴───┤
│ │ │ │ values │ │ │ │ values │ │ │ │ values │ │
└────────────────────────────┘ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤ │ ├───┬───┬──┴┬───┬───┬───┬───┬───┤
│ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ │ │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
│ ├───┴───┴───┴───┴───┴───┴───┴───┤ │ ├───┴───┴───┴───┴───┴───┴───┴───┤ │ ├───┴───┴───┴───┴───┴───┴───┴───┤
│ │ overflow *bmap │ │ │ overflow *bmap │ │ │ overflow *bmap │
│ │ │────┘ │ │───┘ │ │
│ ├─────────┬─────────────────────┘ └───────────────────────────────┘ └───────────────────────────────┘
└─────────────────▶│ bmap │
├─────────┴─────────────────────┐
│ tophash [bucketCnt]uint8 │
│ │
├──────────┬────────────────────┤
│ keys │ │
├───┬───┬──┴┬───┬───┬───┬───┬───┤
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
├───┴───┴──┬┴───┴───┴───┴───┴───┤
│ values │ │
├───┬───┬──┴┬───┬───┬───┬───┬───┤
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
├───┴───┴───┴───┴───┴───┴───┴───┤
│ overflow *bmap │
│ │
└───────────────────────────────┘
复制代码
最后一个问题,有关数组的扩容问题。咱们初始时make(map, 10),其底层的桶数组长度为2。每一个桶能容纳的键值对为8个,也就是说,咱们的桶数组用完了,也只能存储16个键值对,可是,其触发扩容时机大体在何时呢?还要再看看runtime.mapassign_faststr
.
// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
// tooManyOverflowBuckets reports whether noverflow buckets is too many for a map with 1<<B buckets.
// Note that most of these overflow buckets must be in sparse use;
// if use was dense, then we'd have already triggered regular map growth. func tooManyOverflowBuckets(noverflow uint16, B uint8) bool { // If the threshold is too low, we do extraneous work. // If the threshold is too high, maps that grow and shrink can hold on to lots of unused memory. // "too many" means (approximately) as many overflow buckets as regular buckets. // See incrnoverflow for more details. if B > 15 { B = 15 } // The compiler doesn't see here that B < 16; mask B to generate shorter shift code.
return noverflow >= uint16(1)<<(B&15)
}
// growing reports whether h is growing. The growth may be to the same size or bigger.
func (h *hmap) growing() bool {
return h.oldbuckets != nil
}
func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {
...
// Did not find mapping for key. Allocate new cell & add entry.
// If we hit the max load factor or we have too many overflow buckets,
// and we're not already in the middle of growing, start growing. if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) { hashGrow(t, h) goto again // Growing the table invalidates everything, so try again } ... } 复制代码
若是达到了最大的负载因子,或者咱们有太多的溢出桶,同时并无在扩容,那么就开始扩容。 计算过程,len(map)>8 && len(map)>6.5*(1<<B)
就会达到最大负载因子。 hmap的noverflow
字段是用来记录溢出桶个数的,当溢出桶个数达到uint16(1)<<(B&15)
本示例中,B=1,若是溢出桶的数量大于等于2,也就认为溢出桶过多,也会触发响应的扩容。
两种状况官方采用了不一样的扩容方案:
达到最大负载因子,将 B + 1,进而 hmap 的 bucket 数组扩容一倍; 有太多的溢出桶,经过移动 bucket 内容,使其倾向于紧密排列从而提升 bucket 利用率。
最终的哈希表的数据复制是由growWork()
和 evacuate()
来进行的。
固然,有关map的内容,还有不少,越深刻愈加现其实本身的知识储备还有很大的进步空间,更多详细内容,欢迎去曹大github上学习探究。有任何问题,欢迎留言。。。。
本系列文章:
有任何问题,欢迎留言
参考文献: