前言
编程语言中,需要一些存储的复合数据结构,需要快速的支持查找(O(1)),插入,删除等应用场景,比如比如java里的HashMap,python里的Dictionary,scala里的各种Map,go也原生提供了一个类似的数据类型,就叫做map。首先它是个mutable的,也就是说,可以随时对其进行修改。其次,它不是线程安全的。
基本使用
1.初始化和声明
var mapName map[KeyType]ValueType
其中mapName是变量名称,keyType是键的类型,valueType是值的类型,keyType要求的是
可comparable的,值类型可以是任意类型,go 中comparab类型
Boolean values
Integer values
Floating point values
Complex values
String values
Pointer values
Channel values
Interface values
Struct values are comparable if all their fields are comparable
Array values are comparable if values of the array element type are comparable
A value x of non-interface type X and a value t of interface type T are comparable when values of type X are comparable and X implements T
初始化
var mapName = map[string]string{"name":"danny"}
mapName := make(map[string]string, cap)
2.添加
m := make(map[string]sting, 5)
m["name"] = "danny"
往只是声明,没初始化的nil map里面添加元素会panic
3.读取
a,ok := m["name"]
a := m["name"]
如果name存在,就返回那个值,如果不存在,返回类型的0值,也就是说,根据这个value的类型,返回缺省值,比如string,就返回“”,int 就返回0,如果采用第一种方法,存在name 键时候,ok=true,不存在为ok=false
4.删除
delete(m, keyName)
如果keyName存在,删除成功,否则什么都没有发生,nil map删除元素,不报错
5.遍历
for key, value := range m {
fmt.Println("Key:", key, "Value:", value)
}
遍历的顺序是随机的,每次遍历的顺序是不能保证的,nil map 遍历不报错
源码剖析
package main
import "fmt"
func main() {
m := make(map[string]string, 10)
m["name"] = "yang"
if n,ok := m["name"];ok {
fmt.Println(n)
}
delete(m,"name")
}
"".main STEXT size=449 args=0x0 locals=0x90
0x0000 00000 (main.go:5) TEXT "".main(SB), ABIInternal, $144-0
0x0000 00000 (main.go:5) MOVQ (TLS), CX
0x0009 00009 (main.go:5) LEAQ -16(SP), AX
0x000e 00014 (main.go:5) CMPQ AX, 16(CX)
0x0012 00018 (main.go:5) PCDATA $0, $-2
0x0012 00018 (main.go:5) JLS 439
0x0018 00024 (main.go:5) PCDATA $0, $-1
0x0018 00024 (main.go:5) SUBQ $144, SP
0x001f 00031 (main.go:5) MOVQ BP, 136(SP)
0x0027 00039 (main.go:5) LEAQ 136(SP), BP
0x002f 00047 (main.go:5) PCDATA $0, $-2
0x002f 00047 (main.go:5) PCDATA $1, $-2
0x002f 00047 (main.go:5) FUNCDATA $0, gclocals·f6bd6b3389b872033d462029172c8612(SB)
0x002f 00047 (main.go:5) FUNCDATA $1, gclocals·04419f0c21db53e605b2f1217fef60da(SB)
0x002f 00047 (main.go:5) FUNCDATA $2, gclocals·20ee611724cdd32d8302db5d39cb6428(SB)
0x002f 00047 (main.go:5) FUNCDATA $3, "".main.stkobj(SB)
0x002f 00047 (main.go:6) PCDATA $0, $0
0x002f 00047 (main.go:6) PCDATA $1, $1
0x002f 00047 (main.go:6) XORPS X0, X0
0x0032 00050 (main.go:6) MOVUPS X0, ""..autotmp_13+88(SP)
0x0037 00055 (main.go:6) MOVUPS X0, ""..autotmp_13+104(SP)
0x003c 00060 (main.go:6) MOVUPS X0, ""..autotmp_13+120(SP)
0x0041 00065 (main.go:6) PCDATA $0, $1
0x0041 00065 (main.go:6) LEAQ type.map[string]string(SB), AX
0x0048 00072 (main.go:6) PCDATA $0, $0
0x0048 00072 (main.go:6) MOVQ AX, (SP)
0x004c 00076 (main.go:6) MOVQ $10, 8(SP)
0x0055 00085 (main.go:6) PCDATA $0, $2
0x0055 00085 (main.go:6) PCDATA $1, $0
0x0055 00085 (main.go:6) LEAQ ""..autotmp_13+88(SP), CX
0x005a 00090 (main.go:6) PCDATA $0, $0
0x005a 00090 (main.go:6) MOVQ CX, 16(SP)
0x005f 00095 (main.go:6) CALL runtime.makemap(SB)
0x0064 00100 (main.go:6) PCDATA $0, $1
0x0064 00100 (main.go:6) MOVQ 24(SP), AX
0x0069 00105 (main.go:6) PCDATA $1, $2
0x0069 00105 (main.go:6) MOVQ AX, "".m+64(SP)
0x006e 00110 (main.go:7) PCDATA $0, $3
0x006e 00110 (main.go:7) LEAQ type.map[string]string(SB), CX
0x0075 00117 (main.go:7) PCDATA $0, $1
0x0075 00117 (main.go:7) MOVQ CX, (SP)
0x0079 00121 (main.go:7) PCDATA $0, $0
0x0079 00121 (main.go:7) MOVQ AX, 8(SP)
0x007e 00126 (main.go:7) PCDATA $0, $4
0x007e 00126 (main.go:7) LEAQ go.string."name"(SB), DX
0x0085 00133 (main.go:7) PCDATA $0, $0
0x0085 00133 (main.go:7) MOVQ DX, 16(SP)
0x008a 00138 (main.go:7) MOVQ $4, 24(SP)
0x0093 00147 (main.go:7) CALL runtime.mapassign_faststr(SB)
0x0098 00152 (main.go:7) PCDATA $0, $5
0x0098 00152 (main.go:7) MOVQ 32(SP), DI
0x009d 00157 (main.go:7) MOVQ $4, 8(DI)
0x00a5 00165 (main.go:7) PCDATA $0, $-2
0x00a5 00165 (main.go:7) PCDATA $1, $-2
0x00a5 00165 (main.go:7) CMPL runtime.writeBarrier(SB), $0
0x00ac 00172 (main.go:7) JNE 422
0x00b2 00178 (main.go:7) LEAQ go.string."yang"(SB), AX
0x00b9 00185 (main.go:7) MOVQ AX, (DI)
0x00bc 00188 (main.go:8) PCDATA $0, $1
0x00bc 00188 (main.go:8) PCDATA $1, $2
0x00bc 00188 (main.go:8) LEAQ type.map[string]string(SB), AX
0x00c3 00195 (main.go:8) PCDATA $0, $0
0x00c3 00195 (main.go:8) MOVQ AX, (SP)
0x00c7 00199 (main.go:8) PCDATA $0, $2
0x00c7 00199 (main.go:8) MOVQ "".m+64(SP), CX
0x00cc 00204 (main.go:8) PCDATA $0, $0
0x00cc 00204 (main.go:8) MOVQ CX, 8(SP)
0x00d1 00209 (main.go:8) PCDATA $0, $4
0x00d1 00209 (main.go:8) LEAQ go.string."name"(SB), DX
0x00d8 00216 (main.go:8) PCDATA $0, $0
0x00d8 00216 (main.go:8) MOVQ DX, 16(SP)
0x00dd 00221 (main.go:8) MOVQ $4, 24(SP)
0x00e6 00230 (main.go:8) CALL runtime.mapaccess2_faststr(SB)
0x00eb 00235 (main.go:8) PCDATA $0, $1
0x00eb 00235 (main.go:8) MOVQ 32(SP), AX
0x00f0 00240 (main.go:8) MOVQ 8(AX), CX
0x00f4 00244 (main.go:8) MOVQ (AX), AX
0x00f7 00247 (main.go:8) CMPB 40(SP), $0
0x00fc 00252 (main.go:8) JNE 317
0x00fe 00254 (main.go:11) LEAQ type.map[string]string(SB), AX
0x0105 00261 (main.go:11) PCDATA $0, $0
0x0105 00261 (main.go:11) MOVQ AX, (SP)
0x0109 00265 (main.go:11) PCDATA $0, $1
0x0109 00265 (main.go:11) PCDATA $1, $0
0x0109 00265 (main.go:11) MOVQ "".m+64(SP), AX
0x010e 00270 (main.go:11) PCDATA $0, $0
0x010e 00270 (main.go:11) MOVQ AX, 8(SP)
0x0113 00275 (main.go:11) PCDATA $0, $1
0x0113 00275 (main.go:11) LEAQ go.string."name"(SB), AX
0x011a 00282 (main.go:11) PCDATA $0, $0
0x011a 00282 (main.go:11) MOVQ AX, 16(SP)
0x011f 00287 (main.go:11) MOVQ $4, 24(SP)
0x0128 00296 (main.go:11) CALL runtime.mapdelete_faststr(SB)
0x012d 00301 (main.go:12) MOVQ 136(SP), BP
0x0135 00309 (main.go:12) ADDQ $144, SP
0x013c 00316 (main.go:12) RET
0x013d 00317 (main.go:9) PCDATA $1, $2
0x013d 00317 (main.go:9) MOVQ AX, (SP)
0x0141 00321 (main.go:9) MOVQ CX, 8(SP)
0x0146 00326 (main.go:9) CALL runtime.convTstring(SB)
0x014b 00331 (main.go:9) PCDATA $0, $1
0x014b 00331 (main.go:9) MOVQ 16(SP), AX
0x0150 00336 (main.go:9) PCDATA $1, $3
0x0150 00336 (main.go:9) XORPS X0, X0
0x0153 00339 (main.go:9) MOVUPS X0, ""..autotmp_20+72(SP)
0x0158 00344 (main.go:9) PCDATA $0, $3
0x0158 00344 (main.go:9) LEAQ type.string(SB), CX
0x015f 00351 (main.go:9) PCDATA $0, $1
0x015f 00351 (main.go:9) MOVQ CX, ""..autotmp_20+72(SP)
0x0164 00356 (main.go:9) PCDATA $0, $0
0x0164 00356 (main.go:9) MOVQ AX, ""..autotmp_20+80(SP)
涉及主要函数
1.runtime.makemap(SB),初始化
2.runtime.mapassign_faststr(SB) 添加
3.runtime.mapaccess2_faststr(SB) 读取
4.runtime.mapdelete_faststr(SB) 删除
主要结构:hmap, bmap, extra
// 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
}
// 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.
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.
}
bucketCnt = 1 << bucketCntBits //8
hmap:整体结构
count:记录map当前元素个数,len函数返回这个值
flags:读、写、扩容、迭代等标记,用于记录map当前状态
B:用于计算桶大小, bucketSize = 1 << B
noverflow:溢出桶个数,当溢出桶个数过多时,这个值是一个近似值
hash0:计算key哈希值的随机值,保证一个key在不同map中存放的位置是随机的
buckets:当前哈希桶首地址
oldbuckets:旧哈希桶首地址
nevacuate:已迁移哈希桶个数
extra:扩展字段,不一定每个map都需要,后续会详解
bmap(bucket map),描述一个桶,即可以是哈希桶也可以是溢出桶。但下面的结构体只含有一个tophash,用于桶内快速查找,并没有存K/V的地方。从下面定义里面两个Followed定义可以看出,其实bmap只给出了部分字段的描述,后面还有一块存K/V的内存,以及一个指向溢出桶的指针。之所以不给出全部描述,是因为K/V的内存块大小会随着K/V的类型不断变化,无法固定写死,在使用时也只能通过指针偏移的方式去取用。
tophash:一个长度为8的数据,会存放key的hash值的高8位,用于后续快速查找桶内元素
makemap:
// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first 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) //创建hmap
}
h.hash0 = fastrand() //hash0随机种子
// 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) { // hint > 8 && hint > 6.5*2^B
B++
}
h.B = 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
}
// makeBucketArray initializes a backing array for map buckets.
// 1<<b is the minimum number of buckets to allocate.
// dirtyalloc should either be nil or a bucket array previously
// allocated by makeBucketArray with the same t and b parameters.
// If dirtyalloc is nil a new backing array will be alloced and
// otherwise dirtyalloc will be cleared and reused as backing array.
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
base := bucketShift(b)// 2^b
nbuckets := base
// For small b, overflow buckets are unlikely.
// Avoid the overhead of the calculation.
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) //2^(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)) //分配桶,array,放回第一个元素指针
} 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.ptrdata != 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
}
//bucketCnt = 8,loadFactorNum = 13, loadFactorDen = 2
// 13 * 2^B/2 = 6.5*2^B,,,count > 8 && count > 6.5*2^B
func overLoadFactor(count int, B uint8) bool {
return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}
//bucketShift returns 1<<b
func bucketShift(b uint8) uintptr {
// Masking the shift amount allows overflow checks to be elided.
return uintptr(1) << (b & (sys.PtrSize*8 - 1))
}
make(map[k]v, hint) hint > 8 && hint > 6.5*2^B, 计算出B的大小,如果B!=0 (==0,添加元素的时候才懒初始化),正常分配2^B个正式桶,如果b>4会额外预分配2^(B-4)个溢出桶,给nextOverflow。返回桶的首地址.
mapassign_faststr是针对string的优化,逻辑看mapassign函数
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
if h == nil { //nil map赋值会panic
panic(plainError("assignment to entry in nil map"))
}
if raceenabled {
callerpc := getcallerpc()
pc := funcPC(mapassign)
racewritepc(unsafe.Pointer(h), callerpc, pc)
raceReadObjectPC(t.key, key, callerpc, pc)
}
if msanenabled {
msanread(key, t.key.size)
}
if h.flags&hashWriting != 0 { //在写中,读panic,非协程安全的
throw("concurrent map writes")
}
hash := t.hasher(key, uintptr(h.hash0))//根据key获取hash值
// Set hashWriting after calling t.hasher, since t.hasher may panic,
// in which case we have not actually done a write.
h.flags ^= hashWriting //设置写,其实没写
if h.buckets == nil { //B=0下,这个时候初始化
h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
}
again:
bucket := hash & bucketMask(h.B) //1<< h.B - 1
if h.growing() {
growWork(t, h, bucket)
}
b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))//bmap数组,首地址+桶数量*桶大小
top := tophash(hash) //计算tophash
var inserti *uint8
var insertk unsafe.Pointer
var elem unsafe.Pointer
bucketloop:
for {
for i := uintptr(0); i < bucketCnt; i++ { //遍历桶的8个位置,比较tophash
if b.tophash[i] != top {
if isEmpty(b.tophash[i]) && inserti == nil {
inserti = &b.tophash[i]
insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
}
if b.tophash[i] == emptyRest {//找到为空的,没有值的
break bucketloop
}
continue
}
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
if t.indirectkey() {
k = *((*unsafe.Pointer)(k))
}
if !t.key.equal(key, k) {
continue
}
// already have a mapping for key. Update it.
if t.needkeyupdate() {//更新
typedmemmove(t.key, k, key)
}
elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
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 inserti == nil {
// all current buckets are full, allocate a new one.
newb := h.newoverflow(t, b)
inserti = &newb.tophash[0]
insertk = add(unsafe.Pointer(newb), dataOffset)
elem = add(insertk, bucketCnt*uintptr(t.keysize))
}
// store new key/elem at insert position
if t.indirectkey() {
kmem := newobject(t.key)
*(*unsafe.Pointer)(insertk) = kmem
insertk = kmem
}
if t.indirectelem() {
vmem := newobject(t.elem)
*(*unsafe.Pointer)(elem) = vmem
}
typedmemmove(t.key, insertk, key)
*inserti = top
h.count++
done:
if h.flags&hashWriting == 0 {
throw("concurrent map writes")
}
h.flags &^= hashWriting
if t.indirectelem() {
elem = *((*unsafe.Pointer)(elem))
}
return elem
}
// tophash calculates the tophash value for hash.
func tophash(hash uintptr) uint8 {
top := uint8(hash >> (sys.PtrSize*8 - 8))
if top < minTopHash {
top += minTopHash
}
return top
}
//扩容
func hashGrow(t *maptype, h *hmap) {
// If we've hit the load factor, get bigger.
// Otherwise, there are too many overflow buckets,
// so keep the same number of buckets and "grow" laterally.
bigger := uint8(1)
if !overLoadFactor(h.count+1, h.B) {
bigger = 0
h.flags |= sameSizeGrow
}
oldbuckets := h.buckets
newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
flags := h.flags &^ (iterator | oldIterator)
if h.flags&iterator != 0 {
flags |= oldIterator
}
// commit the grow (atomic wrt gc)
h.B += bigger
h.flags = flags
h.oldbuckets = oldbuckets
h.buckets = newbuckets
h.nevacuate = 0
h.noverflow = 0
if h.extra != nil && h.extra.overflow != nil {
// Promote current overflow buckets to the old generation.
if h.extra.oldoverflow != nil {
throw("oldoverflow is not nil")
}
h.extra.oldoverflow = h.extra.overflow
h.extra.overflow = nil
}
if nextOverflow != nil {
if h.extra == nil {
h.extra = new(mapextra)
}
h.extra.nextOverflow = nextOverflow
}
// the actual copying of the hash table data is done incrementally
// by growWork() and evacuate().
}
1 校验map是否正在写,如果是,则直接报"concurrent map writes"错误
2 设置写标志
3 计算key的哈希值
4 如果哈希桶是空的,则创建哈希桶,桶大小为1
5 计算桶链首地址和tophash
6 找到桶链下的所有桶的元素,看key是否已经存在,如果存在,直接把value写入对应位置,跳到步骤10
7 在查找过程中,会记录下桶里面第一个空元素的位置
8 如果没有空位置,则需要申请一个溢出桶,并把该溢出桶挂在该桶链下
9 把K/V插入到空闲位置
10 map元素总个数加1
11 清除写标志
如果,map不在扩容,超过容量或者过多的溢出桶的时候,进行扩容
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 }
bigger := uint8(1)
if !overLoadFactor(h.count+1, h.B) {// 溢出桶过多的扩容
bigger = 0
h.flags |= sameSizeGrow
}
oldbuckets := h.buckets //老的扩容
newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil) //新的地址,溢出大小不变,装载因子过大,两倍
mapaccess2
func mapaccess2(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, bool) {
if raceenabled && h != nil {
callerpc := getcallerpc()
pc := funcPC(mapaccess2)
racereadpc(unsafe.Pointer(h), callerpc, pc)
raceReadObjectPC(t.key, key, callerpc, pc)
}
if msanenabled && h != nil {
msanread(key, t.key.size)
}
if h == nil || h.count == 0 {
if t.hashMightPanic() {
t.hasher(key, 0) // see issue 23734
}
return unsafe.Pointer(&zeroVal[0]), false
}
if h.flags&hashWriting != 0 {
throw("concurrent map read and map write")
}
hash := t.hasher(key, uintptr(h.hash0))
m := bucketMask(h.B)
b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + (hash&m)*uintptr(t.bucketsize)))
if c := h.oldbuckets; c != nil {
if !h.sameSizeGrow() {
// There used to be half as many buckets; mask down one more power of two.
m >>= 1
}
oldb := (*bmap)(unsafe.Pointer(uintptr(c) + (hash&m)*uintptr(t.bucketsize)))
if !evacuated(oldb) {
b = oldb
}
}
top := tophash(hash)
bucketloop:
for ; b != nil; b = b.overflow(t) {
for i := uintptr(0); i < bucketCnt; i++ {
if b.tophash[i] != top {
if b.tophash[i] == emptyRest {
break bucketloop
}
continue
}
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
if t.indirectkey() {
k = *((*unsafe.Pointer)(k))
}
if t.key.equal(key, k) {
e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
if t.indirectelem() {
e = *((*unsafe.Pointer)(e))
}
return e, true
}
}
}
return unsafe.Pointer(&zeroVal[0]), false //返回零值
}
校验map是否正在写,如果是,则直接报"concurrent map read and map write"错误
计算key哈希值,并根据哈希值计算出key所在桶链位置
计算tophash值,便于快速查找
遍历桶链上的每一个桶,并依次遍历桶内的元素
先比较tophash,如果tophash不一致,再比较下一个,如果tophash一致,再比较key值是否相等
如果key值相等,计算出value的地址,并取出value值,并直接返回
如果key值不相等,则继续比较下一个元素,如果所有元素都不匹配,则直接返回value类型的默认值
mapdelete
func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
if raceenabled && h != nil {
callerpc := getcallerpc()
pc := funcPC(mapdelete)
racewritepc(unsafe.Pointer(h), callerpc, pc)
raceReadObjectPC(t.key, key, callerpc, pc)
}
if msanenabled && h != nil {
msanread(key, t.key.size)
}
if h == nil || h.count == 0 {
if t.hashMightPanic() {
t.hasher(key, 0) // see issue 23734
}
return
}
if h.flags&hashWriting != 0 {
throw("concurrent map writes")
}
hash := t.hasher(key, uintptr(h.hash0))
// Set hashWriting after calling t.hasher, since t.hasher may panic,
// in which case we have not actually done a write (delete).
h.flags ^= hashWriting
bucket := hash & bucketMask(h.B)
if h.growing() {
growWork(t, h, bucket)
}
b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
bOrig := b
top := tophash(hash)
search:
for ; b != nil; b = b.overflow(t) {
for i := uintptr(0); i < bucketCnt; i++ {
if b.tophash[i] != top {
if b.tophash[i] == emptyRest {
break search
}
continue
}
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
k2 := k
if t.indirectkey() {
k2 = *((*unsafe.Pointer)(k2))
}
if !t.key.equal(key, k2) {
continue
}
// Only clear key if there are pointers in it.
if t.indirectkey() {
*(*unsafe.Pointer)(k) = nil
} else if t.key.ptrdata != 0 {
memclrHasPointers(k, t.key.size)
}
e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
if t.indirectelem() {
*(*unsafe.Pointer)(e) = nil
} else if t.elem.ptrdata != 0 {
memclrHasPointers(e, t.elem.size)
} else {
memclrNoHeapPointers(e, t.elem.size)
}
b.tophash[i] = emptyOne
// If the bucket now ends in a bunch of emptyOne states,
// change those to emptyRest states.
// It would be nice to make this a separate function, but
// for loops are not currently inlineable.
if i == bucketCnt-1 {
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
goto notLast
}
} else {
if b.tophash[i+1] != emptyRest {
goto notLast
}
}
for {
b.tophash[i] = emptyRest
if i == 0 {
if b == bOrig {
break // beginning of initial bucket, we're done.
}
// Find previous bucket, continue at its last entry.
c := b
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
}
i = bucketCnt - 1
} else {
i--
}
if b.tophash[i] != emptyOne {
break
}
}
notLast:
h.count--
break search
}
}
if h.flags&hashWriting == 0 {
throw("concurrent map writes")
}
h.flags &^= hashWriting
}
1 校验map是否正在写,如果是,则直接报"concurrent map writes"错误
2 设置写标志
3 计算key的哈希值
4 计算桶链首地址和tophash
5 找到桶链下的所有桶的元素,如果找到key,设置标志删除
6 设置该位置的tophash值,emptyOne,并不会释放内存地址
7 map总元素个数减1
8 清除写标志
如果在迁移中,会进行迁移工作,如果是等量扩容,整理,如果是增量扩容,迁移一个bmap
emptyRest = 0 // this cell is empty, and there are no more non-empty cells at higher indexes or overflows. emptyOne = 1 // this cell is empty evacuatedX = 2 // key/elem is valid. Entry has been evacuated to first half of larger table. evacuatedY = 3 // same as above, but evacuated to second half of larger table. evacuatedEmpty = 4 // cell is empty, bucket is evacuated. minTopHash = 5 // minimum tophash for a normal filled cell.