自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用 Go Map 要注意這 1 個細節(jié),避免依賴他!

開發(fā) 后端
今天通過本文,我們將揭開 for range map 輸出的 “神秘” 面紗,看看它內(nèi)部實現(xiàn)到底是怎么樣的,順序到底是怎么樣?

[[396167]]

本文轉(zhuǎn)載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉(zhuǎn)載本文請聯(lián)系腦子進煎魚了公眾號。

大家好,我是煎魚。

最近又有同學(xué)問我這個日經(jīng)話題,想轉(zhuǎn)他文章時,結(jié)果發(fā)現(xiàn)我的公眾號竟然沒有發(fā)過,因此今天我再嘮叨兩句,好讓大家避開這個 “坑”。

有的小伙伴沒留意過 Go map 輸出、遍歷順序,以為它是穩(wěn)定的有序的,會在業(yè)務(wù)程序中直接依賴這個結(jié)果集順序,結(jié)果栽了個大跟頭,吃了線上 BUG。

有的小伙伴知道是無序的,但卻不知道為什么,有的卻理解錯誤?

奇怪的輸出結(jié)果

今天通過本文,我們將揭開 for range map 輸出的 “神秘” 面紗,看看它內(nèi)部實現(xiàn)到底是怎么樣的,順序到底是怎么樣?

開始吸魚之路。

前言

例子如下:

  1. func main() { 
  2.  m := make(map[int32]string) 
  3.  m[0] = "EDDYCJY1" 
  4.  m[1] = "EDDYCJY2" 
  5.  m[2] = "EDDYCJY3" 
  6.  m[3] = "EDDYCJY4" 
  7.  m[4] = "EDDYCJY5" 
  8.  
  9.  for k, v := range m { 
  10.   log.Printf("k: %v, v: %v", k, v) 
  11.  } 

假設(shè)運行這段代碼,輸出的結(jié)果是怎么樣?是有序,還是無序輸出呢?

  1. k: 3, v: EDDYCJY4 
  2. k: 4, v: EDDYCJY5 
  3. k: 0, v: EDDYCJY1 
  4. k: 1, v: EDDYCJY2 
  5. k: 2, v: EDDYCJY3 

從輸出結(jié)果上來講,是非固定順序輸出的,也就是每次都不一樣。但這是為什么呢?

首先建議你先自己想想原因。其次我在面試時聽過一些說法。有人說因為是哈希的所以就是無(亂)序等等說法。當時我是有點 ???

這也是這篇文章出現(xiàn)的原因,希望大家可以一起研討一下,理清這個問題 :)

看一下匯編

  1.    ... 
  2. 0x009b 00155 (main.go:11) LEAQ type.map[int32]string(SB), AX 
  3. 0x00a2 00162 (main.go:11) PCDATA $2, $0 
  4. 0x00a2 00162 (main.go:11) MOVQ AX, (SP) 
  5. 0x00a6 00166 (main.go:11) PCDATA $2, $2 
  6. 0x00a6 00166 (main.go:11) LEAQ ""..autotmp_3+24(SP), AX 
  7. 0x00ab 00171 (main.go:11) PCDATA $2, $0 
  8. 0x00ab 00171 (main.go:11) MOVQ AX, 8(SP) 
  9. 0x00b0 00176 (main.go:11) PCDATA $2, $2 
  10. 0x00b0 00176 (main.go:11) LEAQ ""..autotmp_2+72(SP), AX 
  11. 0x00b5 00181 (main.go:11) PCDATA $2, $0 
  12. 0x00b5 00181 (main.go:11) MOVQ AX, 16(SP) 
  13. 0x00ba 00186 (main.go:11) CALL runtime.mapiterinit(SB) 
  14. 0x00bf 00191 (main.go:11) JMP 207 
  15. 0x00c1 00193 (main.go:11) PCDATA $2, $2 
  16. 0x00c1 00193 (main.go:11) LEAQ ""..autotmp_2+72(SP), AX 
  17. 0x00c6 00198 (main.go:11) PCDATA $2, $0 
  18. 0x00c6 00198 (main.go:11) MOVQ AX, (SP) 
  19. 0x00ca 00202 (main.go:11) CALL runtime.mapiternext(SB) 
  20. 0x00cf 00207 (main.go:11) CMPQ ""..autotmp_2+72(SP), $0 
  21. 0x00d5 00213 (main.go:11) JNE 193 
  22. ... 

我們大致看一下整體過程,重點處理 Go map 循環(huán)迭代的是兩個 runtime 方法,如下:

  • runtime.mapiterinit
  • runtime.mapiternext

但你可能會想,明明用的是 for range 進行循環(huán)迭代,怎么出現(xiàn)了這兩個函數(shù),怎么回事?

看一下轉(zhuǎn)換后

  1. var hiter map_iteration_struct 
  2. for mapiterinit(type, range, &hiter); hiter.key != nil; mapiternext(&hiter) { 
  3.     index_temp = *hiter.key 
  4.     value_temp = *hiter.val 
  5.     index = index_temp 
  6.     value = value_temp 
  7.     original body 

實際上編譯器對于 slice 和 map 的循環(huán)迭代有不同的實現(xiàn)方式,并不是 for 一扔就完事了,還做了一些附加動作進行處理。而上述代碼就是 for range map 在編譯器展開后的偽實現(xiàn)

看一下源碼

runtime.mapiterinit

  1. func mapiterinit(t *maptype, h *hmap, it *hiter) { 
  2.  ... 
  3.  it.t = t 
  4.  it.h = h 
  5.  it.B = h.B 
  6.  it.buckets = h.buckets 
  7.  if t.bucket.kind&kindNoPointers != 0 { 
  8.   h.createOverflow() 
  9.   it.overflow = h.extra.overflow 
  10.   it.oldoverflow = h.extra.oldoverflow 
  11.  } 
  12.  
  13.  r := uintptr(fastrand()) 
  14.  if h.B > 31-bucketCntBits { 
  15.   r += uintptr(fastrand()) << 31 
  16.  } 
  17.  it.startBucket = r & bucketMask(h.B) 
  18.  it.offset = uint8(r >> h.B & (bucketCnt - 1)) 
  19.  it.bucket = it.startBucket 
  20.     ... 
  21.  
  22.  mapiternext(it) 

通過對 mapiterinit 方法閱讀,可得知其主要用途是在 map 進行遍歷迭代時進行初始化動作。共有三個形參,用于讀取當前哈希表的類型信息、當前哈希表的存儲信息和當前遍歷迭代的數(shù)據(jù)

為什么

咱們關(guān)注到源碼中 fastrand 的部分,這個方法名,是不是迷之眼熟。沒錯,它是一個生成隨機數(shù)的方法。再看看上下文:

  1. ... 
  2. // decide where to start 
  3. r := uintptr(fastrand()) 
  4. if h.B > 31-bucketCntBits { 
  5.  r += uintptr(fastrand()) << 31 
  6. it.startBucket = r & bucketMask(h.B) 
  7. it.offset = uint8(r >> h.B & (bucketCnt - 1)) 
  8.  
  9. // iterator state 
  10. it.bucket = it.startBucket 

在這段代碼中,它生成了隨機數(shù)。用于決定從哪里開始循環(huán)迭代。更具體的話就是根據(jù)隨機數(shù),選擇一個桶位置作為起始點進行遍歷迭代

因此每次重新 for range map,你見到的結(jié)果都是不一樣的。那是因為它的起始位置根本就不固定!

runtime.mapiternext

  1. func mapiternext(it *hiter) { 
  2.     ... 
  3.     for ; i < bucketCnt; i++ { 
  4.   ... 
  5.   k := add(unsafe.Pointer(b), dataOffset+uintptr(offi)*uintptr(t.keysize)) 
  6.   v := add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+uintptr(offi)*uintptr(t.valuesize)) 
  7.   ... 
  8.   if (b.tophash[offi] != evacuatedX && b.tophash[offi] != evacuatedY) || 
  9.    !(t.reflexivekey || alg.equal(k, k)) { 
  10.    ... 
  11.    it.key = k 
  12.    it.value = v 
  13.   } else { 
  14.    rk, rv := mapaccessK(t, h, k) 
  15.    if rk == nil { 
  16.     continue // key has been deleted 
  17.    } 
  18.    it.key = rk 
  19.    it.value = rv 
  20.   } 
  21.   it.bucket = bucket 
  22.   if it.bptr != b { 
  23.    it.bptr = b 
  24.   } 
  25.   it.i = i + 1 
  26.   it.checkBucket = checkBucket 
  27.   return 
  28.  } 
  29.  b = b.overflow(t) 
  30.  i = 0 
  31.  goto next 

在上小節(jié)中,咱們已經(jīng)選定了起始桶的位置。接下來就是通過 mapiternext 進行具體的循環(huán)遍歷動作。該方法主要涉及如下:

  1. 從已選定的桶中開始進行遍歷,尋找桶中的下一個元素進行處理
  2. 如果桶已經(jīng)遍歷完,則對溢出桶 overflow buckets 進行遍歷處理

通過對本方法的閱讀,可得知其對 buckets 的遍歷規(guī)則以及對于擴容的一些處理(這不是本文重點。因此沒有具體展開)

總結(jié)

在本文開始,咱們先提出核心討論點:“為什么 Go map 遍歷輸出是不固定順序?”。

經(jīng)過這一番分析,原因也很簡單明了。就是 for range map 在開始處理循環(huán)邏輯的時候,就做了隨機播種...

你想問為什么要這么做?

當然是官方有意為之,因為 Go 在早期(1.0)的時候,雖是穩(wěn)定迭代的,但從結(jié)果來講,其實是無法保證每個 Go 版本迭代遍歷規(guī)則都是一樣的。而這將會導(dǎo)致可移植性問題。

因此,改之。也請不要依賴...

參考

  • Go maps in action

 

責任編輯:武曉燕 來源: 腦子進煎魚了
相關(guān)推薦

2016-12-26 18:51:34

AndroidJavascriptJSONObject

2015-07-16 16:28:02

移動app開發(fā)細節(jié)

2024-03-21 15:01:44

2010-06-10 14:38:30

協(xié)議轉(zhuǎn)換器

2016-09-23 16:09:01

2016-11-24 15:54:06

androidJSONObject

2010-08-23 14:10:38

2021-06-02 09:23:57

Go開發(fā)內(nèi)存

2010-04-02 13:59:57

無線路由器配置

2021-07-21 08:30:29

注冊登陸交互設(shè)計

2019-04-12 09:45:57

Web網(wǎng)絡(luò)線程性能

2015-09-28 11:13:50

2024-09-30 09:56:36

CSV文件Python

2009-04-23 14:30:19

UML建模

2010-09-29 12:59:53

MotorolaJ2ME

2020-08-10 06:47:31

CSSTRouBLe前端

2022-07-13 00:00:47

iOS蘋果系統(tǒng)

2018-05-04 11:22:21

APP運營pushapp卸載

2010-10-12 15:04:52

MySql索引

2022-05-05 09:31:34

Go語言漏洞
點贊
收藏

51CTO技術(shù)棧公眾號