国产片侵犯亲女视频播放_亚洲精品二区_在线免费国产视频_欧美精品一区二区三区在线_少妇久久久_在线观看av不卡

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Golang - Golang Map實現賦值和擴容的示例代碼

Golang Map實現賦值和擴容的示例代碼

2020-06-09 10:59搬磚程序員帶你飛 Golang

這篇文章主要介紹了Golang Map實現賦值和擴容的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

golang map 操作,是map 實現中較復雜的邏輯。因為當賦值時,為了減少hash 沖突鏈的長度過長問題,會做map 的擴容以及數據的遷移。而map 的擴容以及數據的遷移也是關注的重點。

數據結構

首先,我們需要重新學習下map實現的數據結構:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type hmap struct {
 count   int
 flags   uint8
 B     uint8
 noverflow uint16
 hash0   uint32
 buckets  unsafe.Pointer
 oldbuckets unsafe.Pointer
 nevacuate uintptr
 extra *mapextra
}
 
type mapextra struct {
 overflow  *[]*bmap
 oldoverflow *[]*bmap
 nextOverflow *bmap
}

hmap 是 map 實現的結構體。大部分字段在 第一節中已經學習過了。剩余的就是nevacuate 和extra 了。

首先需要了解搬遷的概念:當hash 中數據鏈太長,或者空的bucket 太多時,會操作數據搬遷,將數據挪到一個新的bucket 上,就的bucket數組成為了oldbuckets。bucket的搬遷不是一次就搬完的,是訪問到對應的bucket時才可能會觸發搬遷操作。(這一點是不是和redis 的擴容比較類似,將擴容放在多個訪問上,減少了單次訪問的延遲壓力)

  • nevactuate 標識的是搬遷的位置(也可以考慮為搬遷的進度)。標識目前 oldbuckets 中 (一個 array)bucket 搬遷到哪里了。
  • extra 是一個map 的結構體,nextOverflow 標識的是申請的空的bucket,用于之后解決沖突時使用;overflow 和 oldoverflow 標識溢出的鏈表中正在使用的bucket 數據。old 和非old 的區別是,old 是為搬遷的數據。

理解了大概的數據結構,我們可以學習map的 賦值操作了。

map 賦值操作

map 的賦值操作寫法如下:

?
1
data := mapExample["hello"]

賦值的實現,golang 為了對不同類型k做了優化,下面時一些實現方法:

?
1
2
3
4
5
6
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {}
func mapassign_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {}
func mapassign_fast32ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {}
func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {}
func mapassign_fast64ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer{}
func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {}

內容大同小異,我們主要學習mapassign 的實現。

mapassign 方法的實現是查找一個空的bucket,把key賦值到bucket上,然后把val的地址返回,然后直接通過匯編做內存拷貝。
那我們一步步看是如何找空閑bucket的:

① 在查找key之前,會做異常檢測,校驗map是否未初始化,或正在并發寫操作,如果存在,則拋出異常:(這就是為什么map 并發寫回panic的原因)

?
1
2
3
4
5
6
7
8
if h == nil {
 panic(plainError("assignment to entry in nil map"))
}
// 竟態檢查 和 內存掃描
 
if h.flags&hashWriting != 0 {
 throw("concurrent map writes")
}

② 需要計算key 對應的hash 值,如果buckets 為空(初始化的時候小于一定長度的map 不會初始化數據)還需要初始化一個bucket

?
1
2
3
4
5
6
7
8
9
alg := t.key.alg
hash := alg.hash(key, uintptr(h.hash0))
 
// 為什么需要在hash 后設置flags,因為 alg.hash可能會panic
h.flags ^= hashWriting
 
if h.buckets == nil {
 h.buckets = newobject(t.bucket) // newarray(t.bucket, 1)
}

③ 通過hash 值,獲取對應的bucket。如果map 還在遷移數據,還需要在oldbuckets中找對應的bucket,并搬遷到新的bucket。

?
1
2
3
4
5
6
7
8
9
10
11
// 通過hash 計算bucket的位置偏移
bucket := hash & bucketMask(h.B)
 
// 此處是搬遷邏輯,我們后續詳解
if h.growing() {
 growWork(t, h, bucket)
}
 
// 計算對應的bucket 位置,和top hash 值
b := (*bmap)(unsafe.Pointer(uintptr(h.buckets) + bucket*uintptr(t.bucketsize)))
top := tophash(hash)

④ 拿到bucket之后,還需要按照鏈表方式一個一個查,找到對應的key, 可能是已經存在的key,也可能需要新增。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
for {
 for i := uintptr(0); i < bucketCnt; i++ {
 
  // 若 tophash 就不相等,那就取tophash 中的下一個
  if b.tophash[i] != top {
 
   // 若是個空位置,把kv的指針拿到。
   if isEmpty(b.tophash[i]) && inserti == nil {
    inserti = &b.tophash[i]
    insertk = add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
    val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
   }
 
   // 若后續無數據,那就不用再找坑了
   if b.tophash[i] == emptyRest {
    break bucketloop
   }
   continue
  }
 
  // 若tophash匹配時
 
  k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
  if t.indirectkey() {
   k = *((*unsafe.Pointer)(k))
  }
 
  // 比較k不等,還需要繼續找
  if !alg.equal(key, k) {
   continue
  }
 
  // 如果key 也相等,說明之前有數據,直接更新k,并拿到v的地址就可以了
  if t.needkeyupdate() {
   typedmemmove(t.key, k, key)
  }
  val = add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
  goto done
 }
 // 取下一個overflow (鏈表指針)
 ovf := b.overflow(t)
 if ovf == nil {
  break
 }
 b = ovf
}

總結下這段程序,主要有幾個部分:

a. map hash 不匹配的情況,會看是否是空kv 。如果調用了delete,會出現空kv的情況,那先把地址留下,如果后面也沒找到對應的k(也就是說之前map 里面沒有對應的Key),那就直接用空kv的位置即可。
b. 如果 map hash 是匹配的,需要判定key 的字面值是否匹配。如果不匹配,還需要查找。如果匹配了,那直接把key 更新(因為可能有引用),v的地址返回即可。
c. 如果上面都沒有,那就看下一個bucket

⑤ 插入數據前,會先檢查數據太多了,需要擴容,如果需要擴容,那就從第③開始拿到新的bucket,并查找對應的位置。

?
1
2
3
4
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
}

⑥ 如果剛才看沒有有空的位置,那就需要在鏈表后追加一個bucket,拿到kv。

?
1
2
3
4
5
6
7
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)
 val = add(insertk, bucketCnt*uintptr(t.keysize))
}

⑦ 最后更新tophash 和 key 的字面值, 并解除hashWriting 約束

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 如果非指針數據(也就是直接賦值的數據),還需要申請內存和拷貝
if t.indirectkey() {
 kmem := newobject(t.key)
 *(*unsafe.Pointer)(insertk) = kmem
 insertk = kmem
}
if t.indirectvalue() {
 vmem := newobject(t.elem)
 *(*unsafe.Pointer)(val) = vmem
}
// 更新tophash, k
typedmemmove(t.key, insertk, key)
*inserti = top
 
done:
if h.flags&hashWriting == 0 {
  throw("concurrent map writes")
 }
 h.flags &^= hashWriting
 if t.indirectvalue() {
  val = *((*unsafe.Pointer)(val))
 }
 return val

到這里,map的賦值基本就介紹完了。下面學習下步驟⑤中的map的擴容。

Map 的擴容

有兩種情況下,需要做擴容。一種是存的kv數據太多了,已經超過了當前map的負載。還有一種是overflow的bucket過多了。這個閾值是一個定值,經驗得出的結論,所以我們這里不考究。

當滿足條件后,將開始擴容。如果滿足條件二,擴容后的buckets 的數量和原來是一樣的,說明可能是空kv占據的坑太多了,通過map擴容做內存整理。如果是因為kv 量多導致map負載過高,那就擴一倍的量。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
func hashGrow(t *maptype, h *hmap) {
 bigger := uint8(1)
 // 如果是第二種情況,擴容大小為0
 if !overLoadFactor(h.count+1, h.B) {
  bigger = 0
  h.flags |= sameSizeGrow
 }
 oldbuckets := h.buckets
 
 // 申請一個大數組,作為新的buckets
 newbuckets, nextOverflow := makeBucketArray(t, h.B+bigger, nil)
 
 flags := h.flags &^ (iterator | oldIterator)
 if h.flags&iterator != 0 {
  flags |= oldIterator
 }
 
 // 然后重新賦值map的結構體,oldbuckets 被填充。之后將做搬遷操作
 h.B += bigger
 h.flags = flags
 h.oldbuckets = oldbuckets
 h.buckets = newbuckets
 h.nevacuate = 0
 h.noverflow = 0
 
 // extra 結構體做賦值
 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
 }
}

總結下map的擴容操作。首先拿到擴容的大小,然后申請大數組,然后做些初始化的操作,把老的buckets,以及overflow做切換即可。

map 數據的遷移

擴容完成后,需要做數據的遷移。數據的遷移不是一次完成的,是使用時才會做對應bucket的遷移。也就是逐步做到的數據遷移。下面我們來學習。

在數據賦值的第③步,會看需要操作的bucket是不是在舊的buckets里面,如果在就搬遷。下面是搬遷的具體操作:

?
1
2
3
4
5
6
7
8
9
func growWork(t *maptype, h *hmap, bucket uintptr) {
 // 首先把需要操作的bucket 搬遷
 evacuate(t, h, bucket&h.oldbucketmask())
 
 // 再順帶搬遷一個bucket
 if h.growing() {
  evacuate(t, h, h.nevacuate)
 }
}

nevacuate 標識的是當前的進度,如果都搬遷完,應該和2^B的長度是一樣的(這里說的B是oldbuckets 里面的B,畢竟新的buckets長度可能是2^(B+1))。

在evacuate 方法實現是把這個位置對應的bucket,以及其沖突鏈上的數據都轉移到新的buckets上。

① 先要判斷當前bucket是不是已經轉移。 (oldbucket 標識需要搬遷的bucket 對應的位置)

?
1
2
3
4
5
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.bucketsize)))
// 判斷
if !evacuated(b) {
 // 做轉移操作
}

轉移的判斷直接通過tophash 就可以,判斷tophash中第一個hash值即可 (tophash的作用可以參考第三講)

?
1
2
3
4
5
func evacuated(b *bmap) bool {
 h := b.tophash[0]
 // 這個區間的flag 均是已被轉移
 return h > emptyOne && h < minTopHash
}

② 如果沒有被轉移,那就要遷移數據了。數據遷移時,可能是遷移到大小相同的buckets上,也可能遷移到2倍大的buckets上。這里xy 都是標記目標遷移位置的標記:x 標識的是遷移到相同的位置,y 標識的是遷移到2倍大的位置上。我們先看下目標位置的確定:

?
1
2
3
4
5
6
7
8
9
10
11
12
var xy [2]evacDst
x := &xy[0]
x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.bucketsize)))
x.k = add(unsafe.Pointer(x.b), dataOffset)
x.v = add(x.k, bucketCnt*uintptr(t.keysize))
if !h.sameSizeGrow() {
 // 如果是2倍的大小,就得算一次 y 的值
 y := &xy[1]
 y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.bucketsize)))
 y.k = add(unsafe.Pointer(y.b), dataOffset)
 y.v = add(y.k, bucketCnt*uintptr(t.keysize))
}

③ 確定bucket位置后,需要按照kv 一條一條做遷移。(目的就是清除空閑的kv)

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 遍歷每個bucket
for ; b != nil; b = b.overflow(t) {
 k := add(unsafe.Pointer(b), dataOffset)
 v := add(k, bucketCnt*uintptr(t.keysize))
 
 // 遍歷bucket 里面的每個kv
 for i := 0; i < bucketCnt; i, k, v = i+1, add(k, uintptr(t.keysize)), add(v, uintptr(t.valuesize)) {
  top := b.tophash[i]
 
  // 空的不做遷移
  if isEmpty(top) {
   b.tophash[i] = evacuatedEmpty
   continue
  }
  if top < minTopHash {
   throw("bad map state")
  }
  k2 := k
  if t.indirectkey() {
   k2 = *((*unsafe.Pointer)(k2))
  }
  var useY uint8
  if !h.sameSizeGrow() {
   // 2倍擴容的需要重新計算hash,
   hash := t.key.alg.hash(k2, uintptr(h.hash0))
   if h.flags&iterator != 0 && !t.reflexivekey() && !t.key.alg.equal(k2, k2) {
    useY = top & 1
    top = tophash(hash)
   } else {
    if hash&newbit != 0 {
     useY = 1
    }
   }
  }
 
  // 這些是固定值的校驗,可以忽略
  if evacuatedX+1 != evacuatedY || evacuatedX^1 != evacuatedY {
   throw("bad evacuatedN")
  }
 
  // 設置oldbucket 的tophash 為已搬遷
  b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY
  dst := &xy[useY]         // evacuation destination
  if dst.i == bucketCnt {
   // 如果dst是bucket 里面的最后一個kv,則需要添加一個overflow
   dst.b = h.newoverflow(t, dst.b)
   dst.i = 0
   dst.k = add(unsafe.Pointer(dst.b), dataOffset)
   dst.v = add(dst.k, bucketCnt*uintptr(t.keysize))
  }
  // 填充tophash值, kv 數據
  dst.b.tophash[dst.i&(bucketCnt-1)] = top
  if t.indirectkey() {
   *(*unsafe.Pointer)(dst.k) = k2
  } else {
   typedmemmove(t.key, dst.k, k)
  }
  if t.indirectvalue() {
   *(*unsafe.Pointer)(dst.v) = *(*unsafe.Pointer)(v)
  } else {
   typedmemmove(t.elem, dst.v, v)
  }
 
  // 更新目標的bucket
  dst.i++
  dst.k = add(dst.k, uintptr(t.keysize))
  dst.v = add(dst.v, uintptr(t.valuesize))
 }
}

對于key 非間接使用的數據(即非指針數據),做內存回收

?
1
2
3
4
5
6
7
8
if h.flags&oldIterator == 0 && t.bucket.kind&kindNoPointers == 0 {
 b := add(h.oldbuckets, oldbucket*uintptr(t.bucketsize))
 ptr := add(b, dataOffset)
 n := uintptr(t.bucketsize) - dataOffset
 
 // ptr 是kv的位置, 前面的topmap 保留,做遷移前的校驗使用
 memclrHasPointers(ptr, n)
}

④ 如果當前搬遷的bucket 和 總體搬遷的bucket的位置是一樣的,我們需要更新總體進度的標記 nevacuate

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// newbit 是oldbuckets 的長度,也是nevacuate 的重點
func advanceEvacuationMark(h *hmap, t *maptype, newbit uintptr) {
 // 首先更新標記
 h.nevacuate++
 
 // 最多查看2^10 個bucket
 stop := h.nevacuate + 1024
 if stop > newbit {
  stop = newbit
 }
 
 // 如果沒有搬遷就停止了,等下次搬遷
 for h.nevacuate != stop && bucketEvacuated(t, h, h.nevacuate) {
  h.nevacuate++
 }
 
 // 如果都已經搬遷完了,oldbukets 完全搬遷成功,清空oldbuckets
 if h.nevacuate == newbit {
  h.oldbuckets = nil
  if h.extra != nil {
   h.extra.oldoverflow = nil
  }
  h.flags &^= sameSizeGrow
 }
}

總結

  1. Map 的賦值難點在于數據的擴容和數據的搬遷操作。
  2. bucket 搬遷是逐步進行的,每進行一次賦值,會做至少一次搬遷工作。
  3. 擴容不是一定會新增空間,也有可能是只是做了內存整理。
  4. tophash 的標志即可以判斷是否為空,還會判斷是否搬遷,以及搬遷的位置為X or Y。
  5. delete map 中的key,有可能出現很多空的kv,會導致搬遷操作。如果可以避免,盡量避免。

到此這篇關于Golang Map實現賦值和擴容的示例代碼的文章就介紹到這了,更多相關Golang Map 賦值和擴容內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/-lee/p/12807063.html

延伸 · 閱讀

精彩推薦
  • Golanggolang json.Marshal 特殊html字符被轉義的解決方法

    golang json.Marshal 特殊html字符被轉義的解決方法

    今天小編就為大家分享一篇golang json.Marshal 特殊html字符被轉義的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧 ...

    李浩的life12792020-05-27
  • Golanggolang的httpserver優雅重啟方法詳解

    golang的httpserver優雅重啟方法詳解

    這篇文章主要給大家介紹了關于golang的httpserver優雅重啟的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,...

    helight2992020-05-14
  • Golanggo日志系統logrus顯示文件和行號的操作

    go日志系統logrus顯示文件和行號的操作

    這篇文章主要介紹了go日志系統logrus顯示文件和行號的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    SmallQinYan12302021-02-02
  • GolangGolang中Bit數組的實現方式

    Golang中Bit數組的實現方式

    這篇文章主要介紹了Golang中Bit數組的實現方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    天易獨尊11682021-06-09
  • GolangGolang通脈之數據類型詳情

    Golang通脈之數據類型詳情

    這篇文章主要介紹了Golang通脈之數據類型,在編程語言中標識符就是定義的具有某種意義的詞,比如變量名、常量名、函數名等等,Go語言中標識符允許由...

    4272021-11-24
  • Golanggo語言制作端口掃描器

    go語言制作端口掃描器

    本文給大家分享的是使用go語言編寫的TCP端口掃描器,可以選擇IP范圍,掃描的端口,以及多線程,有需要的小伙伴可以參考下。 ...

    腳本之家3642020-04-25
  • Golanggolang如何使用struct的tag屬性的詳細介紹

    golang如何使用struct的tag屬性的詳細介紹

    這篇文章主要介紹了golang如何使用struct的tag屬性的詳細介紹,從例子說起,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看...

    Go語言中文網11352020-05-21
  • Golanggolang 通過ssh代理連接mysql的操作

    golang 通過ssh代理連接mysql的操作

    這篇文章主要介紹了golang 通過ssh代理連接mysql的操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧...

    a165861639710342021-03-08
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25 Weibo Article 26 Weibo Article 27 Weibo Article 28 Weibo Article 29 Weibo Article 30 Weibo Article 31 Weibo Article 32 Weibo Article 33 Weibo Article 34 Weibo Article 35 Weibo Article 36 Weibo Article 37 Weibo Article 38 Weibo Article 39 Weibo Article 40
主站蜘蛛池模板: 精品综合久久 | 在线观看av国产一区二区 | av成人在线观看 | 成人a在线 | 黄色a视频 | 五月天婷婷免费视频 | 精品无人乱码一区二区三区的优势 | 精品国产乱码久久久久久图片 | 狠狠艹 | 在线欧美亚洲 | 久久久久成人精品免费播放动漫 | 国产福利91精品一区二区三区 | av男人的天堂在线 | 成人不卡视频 | 色婷婷综合网 | 久久精品一 | 亚洲第1页 | 久久综合九色综合网站 | 天堂视频在线 | 永久看片| 黄色短片免费看 | 日本久久精品视频 | 人人99| 日本a视频 | 亚洲一区中文 | 啪啪伊人网| 日本视频一区二区三区 | 中文字幕在线观看日本 | 国产精品视频一区二区三区四 | 亚洲成人一区二区三区 | 亚洲一区二区三区高清 | 久热免费在线观看 | av在线电影网站 | 亚洲国产免费av | 亚洲国产免费 | 亚洲视屏 | 欧美久久视频 | 中文字幕视频一区 | 国产精品成av人在线视午夜片 | 中文字幕在线免费看 | 成人久久久久久 |