成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

[[396167]]

本文轉載自微信公眾號「腦子進煎魚了」,作者陳煎魚。轉載本文請聯系腦子進煎魚了公眾號。

大家好,我是煎魚。

最近又有同學問我這個日經話題,想轉他文章時,結果發現我的公眾號竟然沒有發過,因此今天我再嘮叨兩句,好讓大家避開這個 “坑”。

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

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

奇怪的輸出結果

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

開始吸魚之路。

前言

例子如下:

  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.  } 

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

  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 

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

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

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

看一下匯編

  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 循環迭代的是兩個 runtime 方法,如下:

  • runtime.mapiterinit
  • runtime.mapiternext

但你可能會想,明明用的是 for range 進行循環迭代,怎么出現了這兩個函數,怎么回事?

看一下轉換后

  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 的循環迭代有不同的實現方式,并不是 for 一扔就完事了,還做了一些附加動作進行處理。而上述代碼就是 for range map 在編譯器展開后的偽實現

看一下源碼

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 進行遍歷迭代時進行初始化動作。共有三個形參,用于讀取當前哈希表的類型信息、當前哈希表的存儲信息和當前遍歷迭代的數據

為什么

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

  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 

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

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

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 

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

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

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

總結

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

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

你想問為什么要這么做?

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

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

參考

  • Go maps in action

 

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

2016-12-26 18:51:34

AndroidJavascriptJSONObject

2015-07-16 16:28:02

移動app開發細節

2010-06-10 14:38:30

協議轉換器

2024-03-21 15:01:44

2016-09-23 16:09:01

2016-11-24 15:54:06

androidJSONObject

2010-04-02 13:59:57

無線路由器配置

2010-08-23 14:10:38

2021-06-02 09:23:57

Go開發內存

2021-07-21 08:30:29

注冊登陸交互設計

2019-04-12 09:45:57

Web網絡線程性能

2015-09-28 11:13:50

2024-09-30 09:56:36

CSV文件Python

2010-09-29 12:59:53

MotorolaJ2ME

2009-04-23 14:30:19

UML建模

2020-08-10 06:47:31

CSSTRouBLe前端

2018-05-04 11:22:21

APP運營pushapp卸載

2010-10-12 15:04:52

MySql索引

2022-07-13 00:00:47

iOS蘋果系統

2022-05-05 09:31:34

Go語言漏洞
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 午夜精品在线观看 | 亚洲免费精品 | 国产视频久久久 | 91最新在线视频 | 精品欧美一区二区中文字幕视频 | 成年视频在线观看福利资源 | 91社区视频| 少妇黄色 | 在线视频h| 视频一区二区在线 | 欧美一区2区三区4区公司 | 久久综合一区二区三区 | 精品一区二区三区av | 久久久精品一区 | www.国产精品 | 天天干天天操天天看 | 欧美亚洲国产日韩 | 日韩欧美一级精品久久 | 一区在线播放 | 一起操网站 | 亚洲香蕉在线视频 | 久久久久久久电影 | 精品一区二区免费视频 | 我想看一级黄色毛片 | 国产三级| 欧美在线激情 | 一级黄色片在线免费观看 | av黄色免费 | 欧美日韩中文国产一区发布 | 欧美日韩综合视频 | 欧美在线播放一区 | 亚洲欧美中文日韩在线v日本 | 国产精品久久久久久久久久久免费看 | 日日日日操 | 欧美国产精品 | 日韩一区二区av | 人妖一区| 成人国产精品 | 亚洲精品1区 | 亚洲综合一区二区三区 | 一级黄色毛片子 |