go之map

map

map是啥眉孩?怎么用?這兩個問題搞不清楚的勒葱,不用往下看了浪汪,先弄清楚再說

源碼

先看看參考資料
深度解密Go語言之 map
map筆記
Go源碼學(xué)習(xí)之map
本文源碼解析部分,會做一個全面的解析凛虽,補充上面沒說到或者沒說清楚的

創(chuàng)建
// map的數(shù)據(jù)結(jié)構(gòu)
type hmap struct {
    // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
    // Make sure this stays in sync with the compiler's definition.
    // map中kv的個數(shù)死遭,可以使用len()獲取
    count     int // # live cells == size of map.  Must be first (used by len() builtin)
    // 一些標(biāo)記位
    flags     uint8
    // hash桶的數(shù)量,這里存的是次方凯旋,也就是說2^B才是桶的實際數(shù)量
    // 這么存的好處是可以通過簡單的位運算來計算mask
    B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    // 溢出桶的數(shù)量
    // 所謂溢出桶就是通過鏈地址法解決沖突而產(chǎn)生的桶
    noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    // hash隨機(jī)種子呀潭,用于生成hash隨機(jī)數(shù)
    hash0     uint32 // hash seed

    // 是一個指針钉迷,指向桶,也就是hash數(shù)組
    buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    // 擴(kuò)容時用的钠署,標(biāo)記舊桶
    oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    // 擴(kuò)容時桶的遷移進(jìn)度標(biāo)記糠聪,標(biāo)識當(dāng)前擴(kuò)容遷移到了哪個桶
    nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
    // 可選
    // 用于保存指向新舊溢出桶的指針
    // 還會保存指向備用溢出桶的指針
    extra *mapextra // optional fields
}

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.
    // 指向溢出桶指針切片的指針
    // 當(dāng)桶的kv都不包含指針并且是內(nèi)聯(lián)的,就不需要gc每次都掃描所有的bucket
    // 同時為了保證不讓gc干掉溢出桶谐鼎,就將指向溢出桶的指針存在overflow中
    overflow    *[]*bmap
    oldoverflow *[]*bmap

    // nextOverflow holds a pointer to a free overflow bucket.
    // 指向備用溢出桶的指針舰蟆,具體怎么用后面會說
    nextOverflow *bmap
}

// A bucket for a Go map.
// 這個是hash桶的結(jié)構(gòu)
// hmap.buckets就是指向這里
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.
    // 頂部hash,用于快速篩查以及做一些狀態(tài)標(biāo)記狸棍,具體后面會說到
    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.
}

// 實際編譯期間身害,bmap會經(jīng)過反射生成真正的bmap類型
// 如下是cmd/compile/internal/reflectdata/reflect.go中115行代碼
// 這里不做過多解釋
field := make([]*types.Field, 0, 5)

// The first field is: uint8 topbits[BUCKETSIZE].
arr := types.NewArray(types.Types[types.TUINT8], BUCKETSIZE)
field = append(field, makefield("topbits", arr))

arr = types.NewArray(keytype, BUCKETSIZE)
arr.SetNoalg(true)
keys := makefield("keys", arr)
field = append(field, keys)

arr = types.NewArray(elemtype, BUCKETSIZE)
arr.SetNoalg(true)
elems := makefield("elems", arr)
field = append(field, elems)

// If keys and elems have no pointers, the map implementation
// can keep a list of overflow pointers on the side so that
// buckets can be marked as having no pointers.
// Arrange for the bucket to have no pointers by changing
// the type of the overflow field to uintptr in this case.
// See comment on hmap.overflow in runtime/map.go.
// 這里可以說下,這里會檢查kv是否會包含指針
// 如果kv沒有包含就定義為uintptr類型草戈,從而標(biāo)記桶不包含指針塌鸯,與上面說的mapextra.overflow相呼應(yīng)
// 這個之前在unsafe.Pointer的文章中說過,uintptr底層是整型非指針唐片,而且gc也不會當(dāng)做指針處理
otyp := types.Types[types.TUNSAFEPTR]
if !elemtype.HasPointers() && !keytype.HasPointers() {
    otyp = types.Types[types.TUINTPTR]
}
overflow := makefield("overflow", otyp)
field = append(field, overflow)

// 經(jīng)過上面的一頓操作丙猬,bmap的最終結(jié)構(gòu)大致就如下
// 這里注意kv的內(nèi)存布局,不是k/v/k/v這么存牵触,而是所有k連續(xù)淮悼,所有v連續(xù),即k/k/v/v
// 這么做主要是因為go中的內(nèi)存對齊揽思,具體可看之前的文章
// 假設(shè)map[int64][int8]這種的,如果k/v/k/v這么存见擦,需要的內(nèi)存空間就會比k/k/v/v大
type bmap struct {
    // tophash
    topbits  [8]uint8
    // 存儲k
    keys     [8]keytype
    // 存儲v
    values   [8]valuetype
    // 這里是內(nèi)存空洞钉汗,主要是為了保證overflow在bmap內(nèi)存中的最后位置,具體原因后面會說到
    pad      uintptr
    // 指向下一個桶的指針
    overflow uintptr
}

func makemap(t *maptype, hint int, h *hmap) *hmap {
    // 判斷是否溢出
    // math.MulUintptr這里不贅述了鲤屡,看過之前源碼文章的應(yīng)該知道
    // 這里如果hint太大损痰,導(dǎo)致計算后的字節(jié)數(shù)溢出了的話,會置為0酒来,不會報panic
    mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
    if overflow || mem > maxAlloc {
        hint = 0
    }

    // new一個hmap
    if h == nil {
        h = new(hmap)
    }
    // 生成hash種子
    h.hash0 = fastrand()

    // Find the size parameter B which will hold the requested # of elements.
    // For hint < 0 overLoadFactor returns false since hint < bucketCnt.
    // 通過hint找符合要求的最小B卢未,默認(rèn)B是0,也就是2^0=1
    // overLoadFactor用于判斷hint/2^B是否>負(fù)載因子堰汉,后面會說到
    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.
    // 如果B=0辽社,對應(yīng)的buckets內(nèi)存會延遲分配,在map寫入的時候
    if h.B != 0 {
        var nextOverflow *bmap
        // 調(diào)用makeBucketArray創(chuàng)建buckets翘鸭,具體實現(xiàn)后面會說
        h.buckets, nextOverflow = makeBucketArray(t, h.B, nil)
        // 如果申請了備用桶內(nèi)存滴铅,nextOverflow指針指向該內(nèi)存塊
        if nextOverflow != nil {
            h.extra = new(mapextra)
            h.extra.nextOverflow = nextOverflow
        }
    }
    // 返回hmap
    return h
}

// overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
// 用于判斷hint/2^B是否>負(fù)載因子
// 這里負(fù)載因子是loadFactorNum/loadFactorDen = 13/2 = 6.5
// 即平均每個桶最多能寫入6.5對kv
func overLoadFactor(count int, B uint8) bool {
    return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
}


// 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.
// 構(gòu)建桶數(shù)組,其實就是分配一塊連續(xù)的內(nèi)存
func makeBucketArray(t *maptype, b uint8, dirtyalloc unsafe.Pointer) (buckets unsafe.Pointer, nextOverflow *bmap) {
    // 計算要新建多少個桶就乓,即2^b汉匙,具體實現(xiàn)后面說
    base := bucketShift(b)
    // 把base先保存起來
    nbuckets := base
    // For small b, overflow buckets are unlikely.
    // Avoid the overhead of the calculation.
    // 如果b比較小拱烁,說明新建map的時候需求分配的長度就小,那么需要溢出桶的可能性不大
    // 這里有一個門限值噩翠,如果b >= 4即需要桶的數(shù)量 >= 16戏自,就多申請一些空間備用浦妄,需要溢出桶的可能性比較大
    // 預(yù)留分配玄呛,可以避免后續(xù)頻繁計算分配內(nèi)容,也可以減少內(nèi)存碎片
    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.
        // 預(yù)留2^(b-4)個桶
        nbuckets += bucketShift(b - 4)
        sz := t.bucket.size * nbuckets
        // 計算mallocgc會分配多大的內(nèi)存塊,這里不是你申請多少就剛好分配你多少的,具體原因這里不細(xì)說,跟內(nèi)存分配有關(guān)
        up := roundupsize(sz)
        if up != sz {
            // 以最終分配到的為準(zhǔn)虎韵,看能cover住多少個桶,向下取整
            nbuckets = up / t.bucket.size
        }
    }

    // 這里分兩種情況,第一種是dirtyalloc參數(shù)是nil
    // 這種情況就new一個bucket數(shù)組即可
    // 第二種就是dirtyalloc參數(shù)不是nil绳泉,那就復(fù)用這塊內(nèi)存而不新申請
    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
        // 使用前需要清空內(nèi)存塊數(shù)據(jù)
        // 區(qū)分兩種四苇,一種是包含指針的榆骚,一種是不包含的
        // 這里區(qū)分主要是寫屏障相關(guān)的,不詳細(xì)說了
        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.
        // base是要申請的桶數(shù)纲缓,刨掉這部分占用的內(nèi)存喊废,后面就是多申請的,nextOverflow指向后面的多余部分
        // last是找到內(nèi)存塊最后一個桶的位置工闺,然后讓最后一個桶的overflow指向頭桶(準(zhǔn)確的說是指向指向頭桶的指針),具體實現(xiàn)后面會說
        nextOverflow = (*bmap)(add(buckets, base*uintptr(t.bucketsize)))
        last := (*bmap)(add(buckets, (nbuckets-1)*uintptr(t.bucketsize)))
        last.setoverflow(t, (*bmap)(buckets))
    }
    // 返回頭桶和預(yù)留桶地址
    return buckets, nextOverflow
}

// bucketShift returns 1<<b, optimized for code generation.
// 計算2^b
func bucketShift(b uint8) uintptr {
    // Masking the shift amount allows overflow checks to be elided.
    // 等價于1 << b
    // 這里b & (sys.PtrSize*8 - 1) 等價于 b & (64 - 1) (假設(shè)是64位機(jī)器)
    // 而b & (64 - 1) <= 63颓屑,避免移位溢出,因為64位機(jī)器纫塌,最多只能左移63次
    return uintptr(1) << (b & (sys.PtrSize*8 - 1))
}

// 找到bucket的overflow,bucket的結(jié)構(gòu)是bmap避除,上面說過bmap編譯時候會通過reflect加點料怎披,其中就有overflow
func (b *bmap) setoverflow(t *maptype, ovf *bmap) {
    // 這里強制轉(zhuǎn)成**bmap是沒有問題的握联,都是指針類型斤讥,所以類型是指向(指向bmap的指針)的指針
    *(**bmap)(add(unsafe.Pointer(b), uintptr(t.bucketsize)-sys.PtrSize)) = ovf
}
訪問

map的訪問有5個方法

  • mapaccess1 返回指向h[k]的指針
  • mapaccess2 返回指向h[k]的指針和一個bool類型變量,用于標(biāo)記map中是否有對應(yīng)的key,還記得 v, ok := h[k]的寫法嗎
  • mapaccessK 同時返回kv钞速,用于map遍歷
  • mapaccess1_fat和mapaccess2_fat 這兩都是mapaccess1的包裝,如果map中不存在k商玫,對應(yīng)返回其類型零值狂打,mapaccess2_fat多返回一個bool類型變量,功能同mapaccess2
    看下mapaccess1的源碼(其他的基本差別不大诬辈,不另贅述)
// mapaccess1 returns a pointer to h[key].  Never returns nil, instead
// it will return a reference to the zero object for the elem type if
// the key is not in the map.
// NOTE: The returned pointer may keep the whole map live, so don't
// hold onto it for very long.
// mapaccess1返回指向h[key]的指針酵使,永遠(yuǎn)不會返回nil
// 如果key不存在,會返回一個指向特殊對象的指針焙糟,方便其他方法進(jìn)行判斷
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        pc := funcPC(mapaccess1)
        racereadpc(unsafe.Pointer(h), callerpc, pc)
        raceReadObjectPC(t.key, key, callerpc, pc)
    }
    if msanenabled && h != nil {
        msanread(key, t.key.size)
    }
    // 如果h沒有初始化或者沒有存任何kv對就直接返回特殊指針
    if h == nil || h.count == 0 {
        // 這里會檢測key是否可以被hash
        // 具體原因看 https://github.com/golang/go/issues/23734
        // 大致意思就是如果傳進(jìn)來的k是slice口渔、map或者func這些無法比較的key,應(yīng)該給出具體的錯誤
        // 如果這里沒有這個檢查酬荞,返回對應(yīng)的類型零值搓劫,就會產(chǎn)生規(guī)則混淆
        if t.hashMightPanic() {
            t.hasher(key, 0) // see issue 23734
        }
        // 返回特殊指針
        return unsafe.Pointer(&zeroVal[0])
    }
    // 如果有寫map操作,拋異常混巧,所以map不是并發(fā)安全的
    if h.flags&hashWriting != 0 {
        throw("concurrent map read and map write")
    }
    // 計算hash值
    hash := t.hasher(key, uintptr(h.hash0))
    // 這里m = (1 << b) -1枪向,假設(shè)b=4 那么m=000...001111(即低四位都是1,其余都是0咧党,這里用二進(jìn)制表示)
    m := bucketMask(h.B)
    // 通過hash的二進(jìn)制低b位計算目標(biāo)桶號
    // 假設(shè)b=4秘蛔,hash的低4位是0011,那么(hash&m) = xxx...xxx0011 & 000...001111 = 000...000011 = 3傍衡,即3號桶就是目標(biāo)桶
    b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
    // 這段邏輯是為了兼容map擴(kuò)容深员,擴(kuò)容后面會說到
    // 如果h.oldbuckets不等于nil,說明正在擴(kuò)容
    if c := h.oldbuckets; c != nil {
        // 如果不是等量擴(kuò)容蛙埂,新桶的h.B會加1即桶的數(shù)量翻倍
        // 那么舊桶對應(yīng)的m就是新桶的一半倦畅,需要右移一位
        if !h.sameSizeGrow() {
            // There used to be half as many buckets; mask down one more power of two.
            m >>= 1
        }
        // 找到舊桶中的目標(biāo)桶
        oldb := (*bmap)(add(c, (hash&m)*uintptr(t.bucketsize)))
        // 如果目標(biāo)桶還沒遷移到新桶,就將舊桶的目標(biāo)桶作為最終目標(biāo)桶
        if !evacuated(oldb) {
            b = oldb
        }
    }
    // 通過hash二進(jìn)制高八位得到top绣的,tophash后面會說
    top := tophash(hash)
bucketloop:
    // 遍歷目標(biāo)桶以及對應(yīng)的所有溢出桶
    for ; b != nil; b = b.overflow(t) {
        // 遍歷每個桶的8個kv對
        for i := uintptr(0); i < bucketCnt; i++ {
            // 先看b.tophash數(shù)組中是否有top叠赐,做一次快速篩選
            if b.tophash[i] != top {
                // 這里有一個特殊標(biāo)識emptyRest,代表后面包括所有的桶都是空的屡江,可以直接跳出兩層循環(huán)結(jié)束查找
                // 關(guān)于如何維護(hù)emptyRest芭概,后面會說到
                if b.tophash[i] == emptyRest {
                    break bucketloop
                }
                // 跳過內(nèi)層循環(huán),快速篩選成功
                continue
            }
            // 找到對應(yīng)的k
            // 這里dataOffset其實就是bmap后的第一個字節(jié)的位置惩嘉,具體怎么來的后面會說
            // 還記得上面說的吧罢洲,bmap在編譯期間會加料,就是在bmap后面新增k,v,pad和overflow
            // 這里dataOffset就是第一個k的位置對應(yīng)bmap首字節(jié)的字節(jié)位移
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
            // 如果k存的是指針文黎,就進(jìn)行解引用
            if t.indirectkey() {
                // 這里面用到了*unsafe.Pointer惹苗,請注意*unsafe.Pointer是安全的殿较,所以可以強制轉(zhuǎn)換,就類似(*int)
                // 具體可以看這里 https://go101.org/article/unsafe.html
                // 反正記住兩個go編譯器的規(guī)則
                // 1. 一個安全的指針可以轉(zhuǎn)換為不安全的指針即unsafe.Pointer鸽粉,反之亦然
                // 2. 一個uintptr類型的值可以轉(zhuǎn)換為不安全的指針斜脂,反之亦然,但是要注意如果指針是nil触机,則不應(yīng)該轉(zhuǎn)換為uintptr帚戳,并進(jìn)行算術(shù)運算
                // 對于未知類型的k,我們可以通過如下方式來解引用得到具體的值
                k = *((*unsafe.Pointer)(k))
            }
            // 如果k相等說明找到了
            // 這里就是為什么map的key必須是可以比較的
            if t.key.equal(key, k) {
                // 先跳過bmap的tophash儡首,再跳過8個k片任,再跳過i個v,就是要找的v
                e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                // 如果v存的是指針蔬胯,解引用对供,同上
                if t.indirectelem() {
                    e = *((*unsafe.Pointer)(e))
                }
                // 返回v
                return e
            }
        }
    }
    // 返回不存在標(biāo)識
    return unsafe.Pointer(&zeroVal[0])
}

// tophash calculates the tophash value for hash.
// 計算tophash
func tophash(hash uintptr) uint8 {
    // 取到hash的高8位
    top := uint8(hash >> (sys.PtrSize*8 - 8))
    // minTopHash=5, 如果top < 5,則top += 5
    // 這是因為0,1,2,3,4都是特殊標(biāo)記值氛濒,有另外的作用产场,makemap解讀的時候說過,tophash不僅有快篩的作用舞竿,還會有標(biāo)記的作用
    // 所以當(dāng)計算出來的tophash與標(biāo)記值沖突時京景,進(jìn)行沖突解決
    if top < minTopHash {
        top += minTopHash
    }
    return top
}

// data offset should be the size of the bmap struct, but needs to be
// aligned correctly. For amd64p32 this means 64-bit alignment
// even though pointers are 32 bit.
// 這里通過unsafe.Offsetof來計算bmap的size,是為了適配不同的處理器架構(gòu)
dataOffset = unsafe.Offsetof(struct {
    b bmap
    v int64
}{}.v)
寫入
// Like mapaccess, but allocates a slot for the key if it is not present in the map.
// 寫map
// 里面可能會觸發(fā)初始化操作骗奖,擴(kuò)容操作
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
    // h是nil确徙,直接panic
    // 比如 var m map[int]int,此時m=nil执桌,操作m[1]=1就會panic
    if h == nil {
        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)
    }
    // 寫并發(fā)異常
    if h.flags&hashWriting != 0 {
        throw("concurrent map writes")
    }
    // 計算hash
    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.
    // 這里在寫入計算完hash之后寫入寫標(biāo)記鄙皇,因為計算hash可能會panic,而這個時候map并沒有發(fā)生實際的寫操作
    h.flags ^= hashWriting

    // 還記得makemap吧仰挣,有可能會延遲分配桶伴逸,這里就是延遲分配的地方
    if h.buckets == nil {
        // new一個桶
        h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
    }

again:
    // 尋找目標(biāo)桶
    bucket := hash & bucketMask(h.B)
    // 如果正在擴(kuò)容,那么現(xiàn)在的目標(biāo)桶bucket是在新桶上
    if h.growing() {
        // 保證目標(biāo)桶對應(yīng)的舊桶已經(jīng)遷移
        // growWrok是實際操作桶遷移的函數(shù)
        // 整個map的擴(kuò)容過程中膘壶,舊桶遷移到新桶都是由寫操作(寫入/刪除)觸發(fā)逐步遷移的
        // 這么做也是為了不影響map的正常讀寫速度违柏,類似redis擴(kuò)容,一個套路
        // 具體如何擴(kuò)容后面會說
        growWork(t, h, bucket)
    }
    // 遷移完后就可以直接寫目標(biāo)桶了
    b := (*bmap)(add(h.buckets, bucket*uintptr(t.bucketsize)))
    top := tophash(hash)

    var inserti *uint8
    var insertk unsafe.Pointer
    var elem unsafe.Pointer
bucketloop:
    for {
        // 遍歷目標(biāo)桶的8對kv
        for i := uintptr(0); i < bucketCnt; i++ {
            // 如果tophash[i]不相等香椎,說明要么就是存的別的kv對,要么就是空的
            if b.tophash[i] != top {
                // 如果是空的且預(yù)備寫入位置還沒找到
                // 就將當(dāng)前空的位置作為預(yù)備寫入tophash的位置
                // 這里還需要繼續(xù)往后找禽篱,因為后面可能會找到目標(biāo)kv
                // 刪除操作會導(dǎo)致tophash中間有空檔
                if isEmpty(b.tophash[i]) && inserti == nil {
                    // 預(yù)備寫入tophash的位置
                    inserti = &b.tophash[i]
                    // 預(yù)備寫入k的位置
                    insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
                    // 預(yù)備寫入v的位置
                    elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
                }
                // tophash[i]為空有兩種
                // 一種是當(dāng)前tophash[i]為空畜伐,還有一種是當(dāng)前及后面所有(包括溢出桶)都為空
                // 回想下mapaccess1中說的emptyRest標(biāo)識
                // 如果后面都是空,就不用繼續(xù)找了躺率,跳出兩層循環(huán)
                if b.tophash[i] == emptyRest {
                    break bucketloop
                }
                // 否則就繼續(xù)找
                continue
            }
            // 如果通過tophash快速找到了玛界,就繼續(xù)找到對應(yīng)的k
            k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
            // 這里不再細(xì)說了万矾,上面說過了
            if t.indirectkey() {
                k = *((*unsafe.Pointer)(k))
            }
            // k不相等,繼續(xù)找
            if !t.key.equal(key, k) {
                continue
            }
            // already have a mapping for key. Update it.
            // k相等慎框,找到目標(biāo)kv
            // 這里需要判斷是否把k覆蓋一遍
            // 有點迷惑良狈,就是k都相等了,為啥要覆蓋呢
            // 看看這里cmd/compile/internal/reflectdata/reflect.go
            // 具體我也不是很清楚笨枯,大概意思就是map[0]和map[+0]和map[-0]是一個kv薪丁,但是k不一樣
            if t.needkeyupdate() {
                typedmemmove(t.key, k, key)
            }
            // 找到對應(yīng)v的位置
            elem = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.elemsize))
            // 跳到最后
            goto done
        }
        
        // 后面還有桶就繼續(xù)找下一個桶
        // 沒有桶了就跳出循環(huán),說明需要寫入新的kv
        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.
    // 到這里說明沒找到目標(biāo)kv馅精,也就是說是新插入一對kv严嗜,h.count需要加1,就觸發(fā)判斷是否擴(kuò)容了
    // 如果map過載或者溢出桶太多洲敢,就會觸發(fā)擴(kuò)容漫玄,具體判斷過載和溢出的函數(shù)后面會說
    if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
        // 擴(kuò)容初始化,會判斷是否需要分配新桶
        hashGrow(t, h)
        // 擴(kuò)容準(zhǔn)備好后压彭,就從頭再來一遍
        // 一個是為了觸發(fā)當(dāng)前桶遷移睦优,二是遷移后會在新桶繼續(xù)執(zhí)行寫操作
        goto again // Growing the table invalidates everything, so try again
    }

    // 如果inserti也是nil,說明目標(biāo)桶及其所有的溢出桶都滿了
    // 需要新建一個溢出桶
    if inserti == nil {
        // The current bucket and all the overflow buckets connected to it are full, allocate a new one.
        // 新建一個溢出桶壮不,newoverflow函數(shù)后面會說
        newb := h.newoverflow(t, b)
        // 指定tophash的位置
        inserti = &newb.tophash[0]
        // 指定k的位置
        insertk = add(unsafe.Pointer(newb), dataOffset)
        // 指定v的位置
        elem = add(insertk, bucketCnt*uintptr(t.keysize))
    }

    // store new key/elem at insert position
    // 是否存指針汗盘,indirectkey上面說過,不再細(xì)說
    if t.indirectkey() {
        // 先new一個k
        kmem := newobject(t.key)
        // 再賦值到桶中k所在位置的內(nèi)存中
        *(*unsafe.Pointer)(insertk) = kmem
        // 這里邏輯上同等于insertk = *insertk忆畅,讓insertk指向新申請的k的內(nèi)存塊
        insertk = kmem
    }
    // 同上
    if t.indirectelem() {
        vmem := newobject(t.elem)
        *(*unsafe.Pointer)(elem) = vmem
    }
    // 新插入的kv的k賦值到insertk指向的內(nèi)存
    typedmemmove(t.key, insertk, key)
    // 新桶的tophash[i] = top
    *inserti = top
    // kv對數(shù)加1
    h.count++

done:
    // 最后檢查是否有并發(fā)異常
    if h.flags&hashWriting == 0 {
        throw("concurrent map writes")
    }
    // &^是置零操作衡未,同等于h.flags &= ^hashWriting
    // 清除寫標(biāo)識
    h.flags &^= hashWriting
    if t.indirectelem() {
        // 這里跟上面insertk = kmem的意思是一樣的,就是拿到v的實際值家凯,即一個指向某個內(nèi)存塊的指針
        elem = *((*unsafe.Pointer)(elem))
    }
    // 返回v
    // 這里就結(jié)束了缓醋,有人會問,上面對k進(jìn)行了賦值绊诲,但是沒有對v賦值送粱,只是返回而已
    // 其實這里調(diào)用方拿到v之后,會在外面進(jìn)行賦值掂之,具體可以通過go tool compile編譯一段寫map的代碼看看
    return elem
}

// growing reports whether h is growing. The growth may be to the same size or bigger.
// map擴(kuò)容的時候會創(chuàng)建新桶抗俄,舊桶會變成oldbuckets
// 這里通過判斷是否有oldbuckets來確認(rèn)map是否在擴(kuò)容
func (h *hmap) growing() bool {
    return h.oldbuckets != nil
}

// 擴(kuò)容的遷移操作
func growWork(t *maptype, h *hmap, bucket uintptr) {
    // make sure we evacuate the oldbucket corresponding
    // to the bucket we're about to use
    // 先遷移指定桶
    // evacuate函數(shù),在擴(kuò)容部分會單獨詳細(xì)說
    evacuate(t, h, bucket&h.oldbucketmask())

    // evacuate one more oldbucket to make progress on growing
    // 如果還沒遷移完
    // 每次還會嘗試多遷移一個桶
    if h.growing() {
        evacuate(t, h, h.nevacuate)
    }
}

// 新建一個溢出桶
func (h *hmap) newoverflow(t *maptype, b *bmap) *bmap {
    var ovf *bmap
    // 還記得nextOverflow吧世舰,makemap的時候可能會申請預(yù)備桶空間动雹,就是用nextOverflow來標(biāo)記內(nèi)存位置的
    // 這里派上用場了
    if h.extra != nil && h.extra.nextOverflow != nil {
        // We have preallocated overflow buckets available.
        // See makeBucketArray for more details.
        ovf = h.extra.nextOverflow
        // 再回憶一下makemap,當(dāng)時申請預(yù)備桶空間后跟压,還將最后一個桶的overflow指向頭桶
        // 這里也派上用場了胰蝠,通過這個能判斷是否是預(yù)備桶的最后一個桶
        if ovf.overflow(t) == nil {
            // We're not at the end of the preallocated overflow buckets. Bump the pointer.
            // 如果不是最后一個桶,那就讓nextOverflow往后挪一個桶,即釋放一個預(yù)備桶到正式桶
            h.extra.nextOverflow = (*bmap)(add(unsafe.Pointer(ovf), uintptr(t.bucketsize)))
        } else {
            // This is the last preallocated overflow bucket.
            // Reset the overflow pointer on this bucket,
            // which was set to a non-nil sentinel value.
            // 如果是最后一個桶茸塞,說明預(yù)備桶用完了躲庄,那么nextOverflow指向nil,最后一個桶的overflow指向nil
            ovf.setoverflow(t, nil)
            h.extra.nextOverflow = nil
        }
    } else {
        // 如果沒有申請預(yù)備桶空間或者預(yù)備桶之前就用完了钾虐,就申請一個新的桶
        ovf = (*bmap)(newobject(t.bucket))
    }
    // 更新溢出桶的計數(shù)
    h.incrnoverflow()
    // 這個ptrdata用于判斷bucket是否包含指針
    // ptrdata=0表示不包含噪窘,具體可以看 cmd/compile/internal/types/size.go PtrDataSize函數(shù)
    if t.bucket.ptrdata == 0 {
        // 如果bucket是不包含指針的,那么如makemap所說的效扫,就需要有h.extra.overflow來兜住所有指向溢出桶的指針
        // 檢查并創(chuàng)建一個overflow倔监,createOverflow方法很簡單,不細(xì)說了
        h.createOverflow()
        // 將指向新桶的指針保存到overflow中
        *h.extra.overflow = append(*h.extra.overflow, ovf)
    }
    // 將當(dāng)前桶連接上新的溢出桶
    b.setoverflow(t, ovf)
    return ovf
}


// incrnoverflow increments h.noverflow.
// noverflow counts the number of overflow buckets.
// This is used to trigger same-size map growth.
// See also tooManyOverflowBuckets.
// To keep hmap small, noverflow is a uint16.
// When there are few buckets, noverflow is an exact count.
// When there are many buckets, noverflow is an approximate count.
// 這里會新增noverflow
func (h *hmap) incrnoverflow() {
    // We trigger same-size map growth if there are
    // as many overflow buckets as buckets.
    // We need to be able to count to 1<<h.B.
    // 如果bucket < 2^16荡短,就老老實實加1
    if h.B < 16 {
        h.noverflow++
        return
    }
    // Increment with probability 1/(1<<(h.B-15)).
    // When we reach 1<<15 - 1, we will have approximately
    // as many overflow buckets as buckets.
    // 否則就概率性的加1丐枉,也就是說如果B很大,buckets很多掘托,那么這個值就是個近似值
    // 就是當(dāng)B很大的時候瘦锹,可以適當(dāng)降低因為溢出桶太多而擴(kuò)容的概率
    mask := uint32(1)<<(h.B-15) - 1
    // Example: if h.B == 18, then mask == 7,
    // and fastrand & 7 == 0 with probability 1/8.
    if fastrand()&mask == 0 {
        h.noverflow++
    }
}
擴(kuò)容
// evacDst is an evacuation destination.
// evacDst用于桶遷移
type evacDst struct {
    // 遷移的目標(biāo)桶
    b *bmap          // current destination bucket
    // 當(dāng)前桶的遷移進(jìn)度,即遷移了i對kv
    i int            // key/elem index into b
    // kv的遷移目標(biāo)位置
    k unsafe.Pointer // pointer to current key storage
    e unsafe.Pointer // pointer to current elem storage
}

// 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.
// 判斷溢出桶是否過多
// 如果常規(guī)桶 > 2^15闪盔,那么就判斷溢出桶數(shù)是否 >= 2^15
// 否則就判斷溢出桶是否 >= 常規(guī)桶
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)
}

// 桶擴(kuò)容
// 如果是負(fù)載弯院,那就讓常規(guī)桶的數(shù)量翻倍
// 如果是溢出桶太多,常規(guī)桶的數(shù)量不變泪掀,整理所有桶使得kv排列更緊湊
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.
    // 是否擴(kuò)充常規(guī)桶標(biāo)識
    bigger := uint8(1)
    // 每增加一個kv對听绳,都需要判斷是否需要擴(kuò)容然后調(diào)用該方法進(jìn)行擴(kuò)容
    // 所以是h.count+1
    if !overLoadFactor(h.count+1, h.B) {
        // 如果不是負(fù)載太高,bigger置為0
        bigger = 0
        // 標(biāo)記為等容量擴(kuò)容
        h.flags |= sameSizeGrow
    }
    // 原來的桶標(biāo)記為舊桶
    oldbuckets := h.buckets
    // 申請新桶
    // 新桶的常規(guī)桶數(shù)量是2^(h.B+bigger)异赫,如果bigger=1椅挣,就是翻倍,否則常規(guī)桶數(shù)不變
    newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)

    // 這里是將舊桶的迭代標(biāo)識復(fù)制給新桶塔拳,具體使用在桶的遍歷中會說到
    flags := h.flags &^ (iterator | oldIterator)
    if h.flags&iterator != 0 {
        flags |= oldIterator
    }
    // commit the grow (atomic wrt gc)
    // 更新h.B
    h.B += bigger
    // 更新標(biāo)識
    h.flags = flags
    // 更新舊桶
    h.oldbuckets = oldbuckets
    // 更新新桶
    h.buckets = newbuckets
    // 標(biāo)記桶遷移進(jìn)度
    h.nevacuate = 0
    // 新桶的溢出桶數(shù)量置為0
    h.noverflow = 0

    // 將指向舊溢出桶的指針挪到oldoverflow中
    // 因為overflow需要給新的溢出桶使用
    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
    }
    // 新申請的桶可能申請了預(yù)備桶空間鼠证,nextOverflow指向?qū)?yīng)內(nèi)存塊
    // 不記得了可以回去看下makeBucketArray函數(shù)
    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().
}

// 擴(kuò)容桶遷移
// 參數(shù)里面會有個舊桶編號oldbucket
// 舊桶在寫時觸發(fā)遷移操作
// 該函數(shù)在hashGrow擴(kuò)容之后調(diào)用
func evacuate(t *maptype, h *hmap, oldbucket uintptr) {
    // 根據(jù)桶號找到舊桶
    b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
    // 計算舊桶的常規(guī)桶數(shù)(不包括溢出桶)
    // 其實就是1 << h.B,這里的h.B是舊桶的
    newbit := h.noldbuckets()
    // 如果舊桶沒有遷移到新桶
    if !evacuated(b) {
        // TODO: reuse overflow buckets instead of using new ones, if there
        // is no iterator using the old buckets.  (If !oldIterator.)

        // xy contains the x and y (low and high) evacuation destinations.
        // 定義兩個evacDst靠抑,evacDst其實用來對應(yīng)新桶的前半部分和后半部分
        // 還記得吧量九,過載擴(kuò)容,新桶的h.B = 舊桶的h.B + 1
        // 假設(shè)原來的h.B=3颂碧,只需要取hash的后三位來計算桶編號即可
        // 擴(kuò)容后h.B=4荠列,就需要取hash的后四位來計算桶編號
        // hash的后第四位可能是1/0,如果是0桶編號不變载城,落到新桶的前半部分肌似,如果是1則落到桶的后半部分
        var xy [2]evacDst
        x := &xy[0]
        // xy[0]對應(yīng)前半部分
        // 確定對應(yīng)前半部分的哪個桶
        // 因為前半部分的桶編號不變,所以這里編號就是參數(shù)oldbucket
        x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
        // 對應(yīng)桶的kv起始位置
        x.k = add(unsafe.Pointer(x.b), dataOffset)
        x.e = add(x.k, bucketCnt*uintptr(t.keysize))

        // 如果是過載擴(kuò)容
        if !h.sameSizeGrow() {
            // Only calculate y pointers if we're growing bigger.
            // Otherwise GC can see bad pointers.
            // xy[1]對應(yīng)后半部分
            y := &xy[1]
            // 因為是后半部分诉瓦,所以hash的后第h.B+1位是1(這里的h.B對應(yīng)舊桶)
            // 所以這里加上newbit
            y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
            // 對應(yīng)桶的kv起始位置
            y.k = add(unsafe.Pointer(y.b), dataOffset)
            y.e = add(y.k, bucketCnt*uintptr(t.keysize))
        }

        // 遍歷舊桶以及對應(yīng)的溢出桶
        for ; b != nil; b = b.overflow(t) {
            // 找到kv起始位置
            k := add(unsafe.Pointer(b), dataOffset)
            e := add(k, bucketCnt*uintptr(t.keysize))
            // 遍歷8對kv
            for i := 0; i < bucketCnt; i, k, e = i+1, add(k, uintptr(t.keysize)), add(e, uintptr(t.elemsize)) {
                top := b.tophash[i]
                // 如果tophash[i]是空锈嫩,繼續(xù)找下一個kv對
                if isEmpty(top) {
                    b.tophash[i] = evacuatedEmpty
                    continue
                }
                // 檢查tophash[i]是否是合法值受楼,防止重入
                if top < minTopHash {
                    throw("bad map state")
                }
                // 取到k的值
                k2 := k
                if t.indirectkey() {
                    k2 = *((*unsafe.Pointer)(k2))
                }
                // useY用于標(biāo)識是否使用新桶的后半部分,這取決于k的hash值
                var useY uint8
                // 如果是過載擴(kuò)容
                if !h.sameSizeGrow() {
                    // Compute hash to make our evacuation decision (whether we need
                    // to send this key/elem to bucket x or bucket y).
                    // 計算k的hash值
                    hash := t.hasher(k2, uintptr(h.hash0))
                    // 檢查特殊情況呼寸,具體在背景部分貼的其他文章里面有詳細(xì)描述,這里不再贅述
                    // 大致意思就是對于某些特殊的k猴贰,每次計算出來的hash值都不一樣对雪,所以沒法通過hash值來判斷是放到新桶的前半部分還是后半部分
                    if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.equal(k2, k2) {
                        // If key != key (NaNs), then the hash could be (and probably
                        // will be) entirely different from the old hash. Moreover,
                        // it isn't reproducible. Reproducibility is required in the
                        // presence of iterators, as our evacuation decision must
                        // match whatever decision the iterator made.
                        // Fortunately, we have the freedom to send these keys either
                        // way. Also, tophash is meaningless for these kinds of keys.
                        // We let the low bit of tophash drive the evacuation decision.
                        // We recompute a new random tophash for the next level so
                        // these keys will get evenly distributed across all buckets
                        // after multiple grows.
                        // 約定一種規(guī)則來處理這種特殊的k,隨機(jī)放到新桶前/后半部分
                        useY = top & 1
                        // 因為hash變了米绕,所以top也變了
                        top = tophash(hash)
                    } else {
                        // 通過hash來判斷
                        if hash&newbit != 0 {
                            // 如果判斷是落到后半部分瑟捣,useY = 1
                            useY = 1
                        }
                    }
                }
                // 這里是檢查落到新桶前半部分的標(biāo)記值和落到后半部分的標(biāo)記值是否符合后續(xù)的計算邏輯
                // 因為這兩個標(biāo)記有可能會變,即使變了栅干,也需要符合下面的規(guī)則
                if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
                    throw("bad evacuatedN")
                }
                // 通過useY的不同值來加和出不同的標(biāo)記即evacuatedX和evacuatedY
                // 這就是上面if判斷的原因
                // 同時賦值給tophash[i]來保存標(biāo)記迈套,還記得吧,這就是之前說過的tophash[i]的第二種功能
                b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY
                // 確認(rèn)使用哪個evacDst
                dst := &xy[useY]                 // evacuation destination

                // 這里檢測dst對應(yīng)的桶即dst.b是否已經(jīng)滿了
                // 上面的for循環(huán)是遍歷舊桶碱鳞,而且只有kv對不為空的時候桑李,才會挪動到新桶,dst.i才會+1
                // 通過上述操作窿给,新桶就會跳過舊桶的空kv對贵白,達(dá)到緊湊的效果
                if dst.i == bucketCnt {
                    // 既然當(dāng)前桶滿了,那就新建一個溢出桶
                    dst.b = h.newoverflow(t, dst.b)
                    // dst.i置為0崩泡,重新為新建的溢出桶計數(shù)
                    dst.i = 0
                    // 重置k和e禁荒,指向溢出桶kv的起始位置
                    dst.k = add(unsafe.Pointer(dst.b), dataOffset)
                    dst.e = add(dst.k, bucketCnt*uintptr(t.keysize))
                }
                // 設(shè)置新目標(biāo)桶的tophash
                // 這里通過位與操作來避免溢出
                dst.b.tophash[dst.i&(bucketCnt-1)] = top // mask dst.i as an optimization, to avoid a bounds check
                // 遷移k的值到新目標(biāo)桶
                // 區(qū)分指針和非指針
                if t.indirectkey() {
                    *(*unsafe.Pointer)(dst.k) = k2 // copy pointer
                } else {
                    typedmemmove(t.key, dst.k, k) // copy elem
                }
                // 遷移v的值到新目標(biāo)桶
                if t.indirectelem() {
                    *(*unsafe.Pointer)(dst.e) = *(*unsafe.Pointer)(e)
                } else {
                    typedmemmove(t.elem, dst.e, e)
                }
                // 計數(shù)加1
                dst.i++
                // These updates might push these pointers past the end of the
                // key or elem arrays.  That's ok, as we have the overflow pointer
                // at the end of the bucket to protect against pointing past the
                // end of the bucket.
                // k和e分別向后挪,指向下一個kv對
                dst.k = add(dst.k, uintptr(t.keysize))
                dst.e = add(dst.e, uintptr(t.elemsize))
            }
        }
        // Unlink the overflow buckets & clear key/elem to help GC.
        // 如果舊桶沒有在遍歷并且bucket有指針
        // 就將舊目標(biāo)桶(除了tophash)以及對應(yīng)溢出桶占用的內(nèi)存都釋放掉角撞,輔助gc呛伴,減少內(nèi)存占用
        // 保留目標(biāo)桶的tophash是為了標(biāo)記該桶以及對應(yīng)的溢出桶都已經(jīng)被遷移了
        // 判斷bucket是否包含指針是因為如果bucket不包含指針,那么指向所有溢出桶的指針會保留在oldoverflow中
        // 而oldoverflow保留的是全部溢出桶谒所,也沒法部分刪掉热康,所以即使清空了溢出桶的內(nèi)存,依然有指針指向百炬,gc也沒法回收
        if h.flags&oldIterator == 0 && t.bucket.ptrdata != 0 {
            b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
            // Preserve b.tophash because the evacuation
            // state is maintained there.
            ptr := add(b, dataOffset)
            n := uintptr(t.bucketsize) - dataOffset
            memclrHasPointers(ptr, n)
        }
    }
    // 如果當(dāng)前桶編號和遷移進(jìn)度一致褐隆,就觸發(fā)遷移進(jìn)度的更新
    if oldbucket == h.nevacuate {
        advanceEvacuationMark(h, t, newbit)
    }
}

// 更新桶的遷移進(jìn)度也是一個隨機(jī)+漸進(jìn)的過程
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) {
    // 進(jìn)度加1
    h.nevacuate++
    // Experiments suggest that 1024 is overkill by at least an order of magnitude.
    // Put it in there as a safeguard anyway, to ensure O(1) behavior.
    // 這個主要是為了控制for循環(huán)的次數(shù),以保證map的O(1)操作
    stop := h.nevacuate + 1024
    if stop > newbit {
        stop = newbit
    }
    // 循環(huán)統(tǒng)計遷移的桶數(shù)并更新遷移進(jìn)度
    for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
        h.nevacuate++
    }
    // 最終所有的桶都遷移完畢剖踊,開始清理舊桶
    if h.nevacuate == newbit { // newbit == # of oldbuckets
        // Growing is all done. Free old main bucket array.
        // 釋放舊桶
        h.oldbuckets = nil
        // Can discard old overflow buckets as well.
        // If they are still referenced by an iterator,
        // then the iterator holds a pointers to the slice.
        if h.extra != nil {
            // 清理指向舊溢出桶的指針
            h.extra.oldoverflow = nil
        }
        // 清空等量擴(kuò)容標(biāo)識
        h.flags &^= sameSizeGrow
    }
}
刪除
// 刪除map中的指定kv
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)
  }
  // 前面說過庶弃,不細(xì)說了
  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")
  }

  // 計算k的hash值
  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).
  // 設(shè)置寫標(biāo)記
  h.flags ^= hashWriting

  // 跟mapassign同樣的操作
  // 獲取桶,判斷是否擴(kuò)容德澈,桶遷移等一系列操作
  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:
  // 遍歷目標(biāo)桶及溢出桶找需要刪除的kv
  for ; b != nil; b = b.overflow(t) {
    for i := uintptr(0); i < bucketCnt; i++ {
      if b.tophash[i] != top {
        // emptyRest標(biāo)記前面說過
        if b.tophash[i] == emptyRest {
          break search
        }
        continue
      }
      // tophash篩選找到歇攻,再判斷k是否相等
      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.
      // 如果k保存的是指針,直接指向nil
      // 如果k包含指針梆造,比如是個struct缴守,包著指針葬毫,就調(diào)用對應(yīng)方法清理內(nèi)存
      if t.indirectkey() {
        *(*unsafe.Pointer)(k) = nil
      } else if t.key.ptrdata != 0 {
        memclrHasPointers(k, t.key.size)
      }
      // 找到k對應(yīng)的v
      // 同樣的操作,清空v
      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)
      }
      // 清空完之后更新tophash[i]
      // 標(biāo)識空屡穗,用于快速篩選
      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.
      // 后面這些操作主要是為了維護(hù)emptyRest標(biāo)志贴捡,該標(biāo)志代表的意思前面說過了
      // 怎么維護(hù)呢,先以被刪除的kv對的位置i向后看一位
      // 兩種情況村砂,第一種被刪除的kv是該桶的最后一個kv對烂斋,那么如果有溢出桶就繼續(xù)看溢出桶的tophash[0]標(biāo)識,否則就向前追溯
      // 第二種被刪除的kv不是該桶的最后一個kv對础废,那就看后一個tophash標(biāo)識即tophash[i+1]
      // 如果tophash[i+1]是emptyRest汛骂,就向前追溯
      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
        }
      }
      // 這就是追溯過程
      // 通過上面分析可知如果tophash[i+1]或者溢出桶的tophash[0]等于emptyRest
      // 那么當(dāng)前的tophash[i]也可設(shè)置為emptyRest
      // 既然當(dāng)前的tophash[i]是emptyRest,那么前面的所有連續(xù)的n個等于emptyOne的空的tophash也都可以設(shè)置成emptyRest
      // 有點類似抽屜原理评腺,不了解的可以看看raft協(xié)議
      for {
        // 當(dāng)前tophash設(shè)置為emptyRest
        b.tophash[i] = emptyRest
        // 如果是頭部kv
        if i == 0 {
          // 如果是頭桶帘瞭,說明追溯完了
          if b == bOrig {
            break // beginning of initial bucket, we're done.
          }
          // Find previous bucket, continue at its last entry.
          // 否則就找當(dāng)前桶的前面那個桶
          // 因為桶之間只有單向順序指針連接,所以這里需要通過遍歷來找
          c := b
          for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
          }
          // 找到上個桶后蒿讥,從最后一個kv對逐步逆序檢查tophash標(biāo)識
          i = bucketCnt - 1
        } else {
          // 如果不是頭部kv蝶念,繼續(xù)追溯
          i--
        }
        // 一旦找到某個tophash[i]不是空,則跳出循環(huán)诈悍,追溯完畢
        if b.tophash[i] != emptyOne {
          break
        }
      }
    notLast:
      // map的元素個數(shù)減一
      h.count--
      // Reset the hash seed to make it more difficult for attackers to
      // repeatedly trigger hash collisions. See issue 25237.
      // 如果map被清空了祸轮,就重置hash種子
      // 這么做可以提升hash沖突的攻擊難度
      // 具體原因見 https://github.com/golang/go/issues/25237
      if h.count == 0 {
        h.hash0 = fastrand()
      }
      // 跳出外層循環(huán)
      break search
    }
  }

  // 這里跟mapassign一樣的,不細(xì)說了
  if h.flags&hashWriting == 0 {
    throw("concurrent map writes")
  }
  h.flags &^= hashWriting
}
清空
// mapclear deletes all keys from a map.
// 清空整個map
func mapclear(t *maptype, h *hmap) {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        pc := funcPC(mapclear)
        racewritepc(unsafe.Pointer(h), callerpc, pc)
    }

    // 本來就是空的
    if h == nil || h.count == 0 {
        return
    }

    // 并發(fā)寫控制
    if h.flags&hashWriting != 0 {
        throw("concurrent map writes")
    }
    // 設(shè)置寫標(biāo)志
    h.flags ^= hashWriting

    // 清空等量擴(kuò)容標(biāo)志
    h.flags &^= sameSizeGrow
    // 清空舊桶侥钳,數(shù)據(jù)都清空了适袜,就沒必要擴(kuò)容了,舊桶也就不需要了
    h.oldbuckets = nil
    // 清空各種計數(shù)器舷夺,遷移進(jìn)度計數(shù)器苦酱,溢出桶計數(shù)器和kv對數(shù)量計數(shù)器
    h.nevacuate = 0
    h.noverflow = 0
    h.count = 0

    // Reset the hash seed to make it more difficult for attackers to
    // repeatedly trigger hash collisions. See issue 25237.
    // 重置hash種子,原因之前說過了
    h.hash0 = fastrand()

    // Keep the mapextra allocation but clear any extra information.
    // 清空擴(kuò)展信息给猾,釋放內(nèi)存
    // 包括指向新舊溢出桶的所有指針和指向預(yù)備桶內(nèi)存塊指針
    if h.extra != nil {
        *h.extra = mapextra{}
    }

    // makeBucketArray clears the memory pointed to by h.buckets
    // and recovers any overflow buckets by generating them
    // as if h.buckets was newly alloced.
    // 通過makeBucketArray來清空并復(fù)用map的buckets
    // 這里的buckets就是之前通過該方法申請的
    _, nextOverflow := makeBucketArray(t, h.B, h.buckets)
    // 還記得makeBucketArray會多申請一部分空間作為預(yù)備桶吧
    // 這里只進(jìn)行清空操作疫萤,之前哪些是常規(guī)桶空間哪些是預(yù)備桶空間不變,方便再次寫入kv時減少溢出桶的申請次數(shù)
    if nextOverflow != nil {
        // If overflow buckets are created then h.extra
        // will have been allocated during initial bucket creation.
        h.extra.nextOverflow = nextOverflow
    }

    // 并發(fā)寫控制
    if h.flags&hashWriting == 0 {
        throw("concurrent map writes")
    }
    // 清空寫標(biāo)記
    h.flags &^= hashWriting
}

這里有人可能比較困惑敢伸,因為map并未提供清空操作扯饶,mapclear何時會調(diào)用呢,這里先看一段代碼

func main() {
    m := make(map[int]int, 2)
    for i := 0; i < 5; i++ {
        m[i] = i
    }
    for k := range m {
        delete(m, k)
    }
}

通過go tool compile -S編譯出來的匯編代碼中有一行如下

image.png

也就是說池颈,編譯器做了優(yōu)化尾序,調(diào)用了mapclear而不是通過迭代進(jìn)行了清空,如果把for k := range m改成for k,_ := range m或者for k,v := range m躯砰,編譯器就不會進(jìn)行優(yōu)化每币,會選擇迭代的方式進(jìn)行清空,所以寫法上需要注意

遍歷
// A hash iteration structure.
// If you modify hiter, also change cmd/compile/internal/reflectdata/reflect.go to indicate
// the layout of this structure.
// 用于map迭代的數(shù)據(jù)結(jié)構(gòu)
type hiter struct {
    // 當(dāng)前迭代的k琢歇,如果key=nil兰怠,說明迭代結(jié)束了
    key         unsafe.Pointer // Must be in first position.  Write nil to indicate iteration end (see cmd/compile/internal/walk/range.go).
    // 當(dāng)前迭代的v
    elem        unsafe.Pointer // Must be in second position (see cmd/compile/internal/walk/range.go).
    t           *maptype
    h           *hmap
    buckets     unsafe.Pointer // bucket ptr at hash_iter initialization time
    // 指向當(dāng)前正在遍歷的桶
    bptr        *bmap          // current bucket
    overflow    *[]*bmap       // keeps overflow buckets of hmap.buckets alive
    oldoverflow *[]*bmap       // keeps overflow buckets of hmap.oldbuckets alive
    // 標(biāo)記從哪個桶開始遍歷梦鉴,每次是隨機(jī)的
    startBucket uintptr        // bucket iteration started at
    // 標(biāo)記總遍歷桶的那個kv對開始遍歷,每次也是隨機(jī)的
    offset      uint8          // intra-bucket offset to start from during iteration (should be big enough to hold bucketCnt-1)
    // 標(biāo)記是否從最后一個桶回繞到了第一個桶
    wrapped     bool           // already wrapped around from end of bucket array to beginning
    B           uint8
    i           uint8
    bucket      uintptr
    checkBucket uintptr
}


// mapiterinit initializes the hiter struct used for ranging over maps.
// The hiter struct pointed to by 'it' is allocated on the stack
// by the compilers order pass or on the heap by reflect_mapiterinit.
// Both need to have zeroed hiter since the struct contains pointers.
// 初始化hiter
func mapiterinit(t *maptype, h *hmap, it *hiter) {
    if raceenabled && h != nil {
        callerpc := getcallerpc()
        racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapiterinit))
    }

    if h == nil || h.count == 0 {
        return
    }

    if unsafe.Sizeof(hiter{})/sys.PtrSize != 12 {
        throw("hash_iter size incorrect") // see cmd/compile/internal/reflectdata/reflect.go
    }
    it.t = t
    it.h = h

    // grab snapshot of bucket state
    // 保留快照
    it.B = h.B
    it.buckets = h.buckets
    if t.bucket.ptrdata == 0 {
        // Allocate the current slice and remember pointers to both current and old.
        // This preserves all relevant overflow buckets alive even if
        // the table grows and/or overflow buckets are added to the table
        // while we are iterating.
        h.createOverflow()
        it.overflow = h.extra.overflow
        it.oldoverflow = h.extra.oldoverflow
    }

    // decide where to start
    // 找隨機(jī)開始位置
    r := uintptr(fastrand())
    if h.B > 31-bucketCntBits {
        r += uintptr(fastrand()) << 31
    }
    it.startBucket = r & bucketMask(h.B)
    it.offset = uint8(r >> h.B & (bucketCnt - 1))

    // iterator state
    // bucket用于記錄當(dāng)前遍歷的桶序號揭保,剛開始等于startBucket
    // 注意it.bucket始終都是新桶的編號肥橙,即使桶擴(kuò)容了
    // 遍歷的順序就是從新桶的第bucket個桶開始(即startBucket),縱向向下遍歷所有溢出桶掖举,遍歷完后橫向向右繼續(xù)遍歷其他常規(guī)桶快骗,遍歷到尾部后繼續(xù)回繞到頭桶,重復(fù)縱向橫向遍歷直到回到startBucket號桶
    // 遍歷每一個bucket號桶的時候還需要檢查是否是在擴(kuò)容塔次,如果在擴(kuò)容,還需要找到對應(yīng)未遷移的舊桶名秀,對舊桶進(jìn)行縱向遍歷
    it.bucket = it.startBucket

    // Remember we have an iterator.
    // Can run concurrently with another mapiterinit().
    // 設(shè)置迭代標(biāo)識
    if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
        atomic.Or8(&h.flags, iterator|oldIterator)
    }

    // 開始迭代
    mapiternext(it)
}


func mapiternext(it *hiter) {
    h := it.h
    if raceenabled {
        callerpc := getcallerpc()
        racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapiternext))
    }
    if h.flags&hashWriting != 0 {
        throw("concurrent map iteration and map write")
    }
    t := it.t
    bucket := it.bucket
    b := it.bptr
    i := it.i
    checkBucket := it.checkBucket

next:
    // 如果當(dāng)前桶是nil励负,也就是說遍歷完了頭桶和對應(yīng)的所有溢出桶
    if b == nil {
        // 檢查是否遍歷了一圈回到了初始遍歷的那個桶
        // 以此來判斷是否遍歷結(jié)束
        if bucket == it.startBucket && it.wrapped {
            // end of iteration
            it.key = nil
            it.elem = nil
            return
        }
        // 如果在擴(kuò)容期間進(jìn)行的迭代
        if h.growing() && it.B == h.B {
            // Iterator was started in the middle of a grow, and the grow isn't done yet.
            // If the bucket we're looking at hasn't been filled in yet (i.e. the old
            // bucket hasn't been evacuated) then we need to iterate through the old
            // bucket and only return the ones that will be migrated to this bucket.
            // 找到對應(yīng)的舊桶
            // 因為擴(kuò)容期間饺蔑,舊桶有可能還沒有遷移到新桶略吨,那么我們就需要遍歷舊桶
            oldbucket := bucket & it.h.oldbucketmask()
            b = (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
            // 如果舊桶沒有遷移
            if !evacuated(b) {
                // 將舊桶和當(dāng)前桶映射起來
                checkBucket = bucket
            } else {
                // 否則就遍歷當(dāng)前桶
                b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
                // 標(biāo)記無舊桶需要遍歷
                checkBucket = noCheck
            }
        } else {
            // 如果沒有在擴(kuò)容秽之,同上
            b = (*bmap)(add(it.buckets, bucket*uintptr(t.bucketsize)))
            checkBucket = noCheck
        }
        // 遍歷桶編號加1
        bucket++
        // 如果最后一個桶都遍歷完了河质,就回繞到頭桶并且打上回繞標(biāo)志
        if bucket == bucketShift(it.B) {
            bucket = 0
            it.wrapped = true
        }
        // 每次遍歷桶的時候,i都從0開始,用于遍歷該桶的8對kv
        i = 0
    }
    for ; i < bucketCnt; i++ {
        // 通過offset來打亂kv遍歷的順序
        offi := (i + it.offset) & (bucketCnt - 1)
        // 如果是空的,就跳過繼續(xù)遍歷下一對kv
        if isEmpty(b.tophash[offi]) || b.tophash[offi] == evacuatedEmpty {
            // TODO: emptyRest is hard to use here, as we start iterating
            // in the middle of a bucket. It's feasible, just tricky.
            continue
        }
        k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize))
        if t.indirectkey() {
            k = *((*unsafe.Pointer)(k))
        }
        e := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.elemsize))
        // 如果當(dāng)前遍歷的是舊桶并且不是等容量擴(kuò)容
        // 需要判斷舊桶的kv對應(yīng)到新桶是不是bucket號桶
        // 還記得擴(kuò)容遷移吧蛇捌,舊桶的kv遷移到新桶是可能落到前半部分和后半部分的
        // 如果當(dāng)前遍歷到的kv落到新桶不是bucket號桶,那就直接跳過了
        if checkBucket != noCheck && !h.sameSizeGrow() {
            // Special case: iterator was started during a grow to a larger size
            // and the grow is not done yet. We're working on a bucket whose
            // oldbucket has not been evacuated yet. Or at least, it wasn't
            // evacuated when we started the bucket. So we're iterating
            // through the oldbucket, skipping any keys that will go
            // to the other new bucket (each oldbucket expands to two
            // buckets during a grow).
            // 這里面區(qū)分兩種情況允粤,一種hash(k)是穩(wěn)定的
            // 另一種hash(k)是不穩(wěn)定的脑蠕,比如math.NaN()
            // 具體規(guī)則擴(kuò)容遷移的時候說過了,這里不贅述了
            if t.reflexivekey() || t.key.equal(k, k) {
                // If the item in the oldbucket is not destined for
                // the current new bucket in the iteration, skip it.
                hash := t.hasher(k, uintptr(h.hash0))
                if hash&bucketMask(it.B) != checkBucket {
                    continue
                }
            } else {
                // Hash isn't repeatable if k != k (NaNs).  We need a
                // repeatable and randomish choice of which direction
                // to send NaNs during evacuation. We'll use the low
                // bit of tophash to decide which way NaNs go.
                // NOTE: this case is why we need two evacuate tophash
                // values, evacuatedX and evacuatedY, that differ in
                // their low bit.
                if checkBucket>>(it.B-1) != uintptr(b.tophash[offi]&1) {
                    continue
                }
            }
        }
        // 如果當(dāng)前遍歷到的kv沒有遷移到新桶阐滩,則賦值給it饱溢,遍歷完成
        // 還有一種情況就是key!=key,比如上面說的math.NaN(),這種key沒法刪除和更新翁逞,也訪問不到(除了遍歷)
        // 所以不會被刪除必怜,可以直接取
        if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) ||
            !(t.reflexivekey() || t.key.equal(k, k)) {
            // This is the golden data, we can return it.
            // OR
            // key!=key, so the entry can't be deleted or updated, so we can just return it.
            // That's lucky for us because when key!=key we can't look it up successfully.
            it.key = k
            if t.indirectelem() {
                e = *((*unsafe.Pointer)(e))
            }
            it.elem = e
        } else {
            // The hash table has grown since the iterator was started.
            // The golden data for this key is now somewhere else.
            // Check the current hash table for the data.
            // This code handles the case where the key
            // has been deleted, updated, or deleted and reinserted.
            // NOTE: we need to regrab the key as it has potentially been
            // updated to an equal() but not identical key (e.g. +0.0 vs -0.0).
            // 遍歷過程中,當(dāng)前k被刪除/更新/刪除后又重新插入等
            // 就需要重新找露久,具體怎么找征峦,mapaccessK后面會說
            rk, re := mapaccessK(t, h, k)
            if rk == nil {
                continue // key has been deleted
            }
            it.key = rk
            it.elem = re
        }
        // 保留迭代現(xiàn)場
        // 遍歷到哪個桶了迟几,實際遍歷的是哪個桶(可能新桶可能舊桶),新舊桶是否有映射以及桶內(nèi)kv的遍歷進(jìn)度
        // 用于下次繼續(xù)遍歷
        it.bucket = bucket
        if it.bptr != b { // avoid unnecessary write barrier; see issue 14921
            it.bptr = b
        }
        it.i = i + 1
        it.checkBucket = checkBucket
        return
    }
    // 當(dāng)前桶沒遍歷完了眶痰,繼續(xù)下一個溢出桶瘤旨,除了i其他不變
    b = b.overflow(t)
    i = 0
    // 繼續(xù)遍歷
    goto next
}


// returns both key and elem. Used by map iterator
// 比較簡單,其實就是通過key找到對應(yīng)的新桶好舊桶
// 如果舊桶沒有遷移就從舊桶找
// 否則就從新桶找
// 找的過程跟mapaccess1沒啥區(qū)別
// 就是一個桶一個桶的遍歷竖伯,遇到emptyRest就跳出外循環(huán)
// 然后對比k是否相等
// 這里就不詳細(xì)注釋了
func mapaccessK(t *maptype, h *hmap, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer) {
    if h == nil || h.count == 0 {
        return nil, nil
    }
    hash := t.hasher(key, uintptr(h.hash0))
    m := bucketMask(h.B)
    b := (*bmap)(add(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)(add(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 k, e
            }
        }
    }
    return nil, nil
}

總結(jié)

至于總結(jié)存哲,就引用下這個吧,我寫累了七婴,需要休息會~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祟偷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子打厘,更是在濱河造成了極大的恐慌修肠,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件户盯,死亡現(xiàn)場離奇詭異嵌施,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)莽鸭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門吗伤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人硫眨,你說我怎么就攤上這事足淆。” “怎么了礁阁?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵巧号,是天一觀的道長。 經(jīng)常有香客問我姥闭,道長丹鸿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任泣栈,我火速辦了婚禮卜高,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘南片。我一直安慰自己掺涛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布疼进。 她就那樣靜靜地躺著薪缆,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拣帽,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天疼电,我揣著相機(jī)與錄音,去河邊找鬼减拭。 笑死蔽豺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拧粪。 我是一名探鬼主播修陡,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼可霎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起癣朗,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤拾因,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旷余,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绢记,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年正卧,在試婚紗的時候發(fā)現(xiàn)自己被綠了庭惜。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡穗酥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惠遏,到底是詐尸還是另有隱情砾跃,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布节吮,位于F島的核電站抽高,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏透绩。R本人自食惡果不足惜翘骂,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帚豪。 院中可真熱鬧碳竟,春花似錦、人聲如沸狸臣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烛亦。三九已至诈泼,卻和暖如春懂拾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铐达。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工岖赋, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瓮孙。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓唐断,卻偏偏與公主長得像,于是被迫代替她去往敵國和親衷畦。 傳聞我的和親對象是個殘疾皇子栗涂,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容