有些小伙伴从来没有关注过Gomap的输出顺序,认为它是稳定有序的;有的朋友知道是坏了,但是不知道为什么?有些误会?今天我们就借着这篇文章来揭开forrangemap的“玄机”,看看它的内部实现是怎样的,输出顺序是怎样的?原文地址:为什么遍历Gomap是无序的?前言funcmain(){m:=make(map[int32]string)m[0]="EDDYCJY1"m[1]="EDDYCJY2"m[2]="EDDYCJY3"m[3]="EDDYCJY4"m[4]="EDDYCJY5"fork,v:=rangem{log.Printf("k:%v,v:%v",k,v)}}假设运行这段代码,输出结果在命令?还是无序输出?2019/04/0323:27:29k:3,v:EDDYCJY42019/04/0323:27:29k:4,v:EDDYCJY52019/04/0323:27:29k:0,v:EDDYCJY12019/04/0323:27:29k:1,v:EDDYCJY22019/04/0323:27:29k:2,v:EDDYCJY3在输出结果上,输出顺序不固定,即每次都不一样(标题也会说话)。但为什么?首先,我建议你自己想想原因。其次,我在采访中听到了一些说法。有人说因为它是散列的,所以它没有(随机)顺序等等。我有点???这也是这篇文章的起因,希望大家一起讨论,弄清楚这个问题:)看编译...0x009b00155(main.go:11)LEAQtype.map[int32]string(SB),AX0x00a200162(main.go:11)PCDATA$2,$00x00a200162(main.go:11)MOVQAX,(SP)0x00a600166(main.go:11)PCDATA$2,$20x00a600166(main.go:11))LEAQ""..autotmp_3+24(SP),AX0x00ab00171(main.go:11)PCDATA$2,$00x00ab00171(main.go:11)MOVQAX,8(SP)0x00b000176(main.go:11)PCDATA$2,$20x00b000176(main.go:11)LEAQ"..autotmp_2+72(SP),AX0x00b500181(main.go:11)PCDATA$2,$00x00b500181(main.go:11)MOVQAX,16(SP)0x00ba00186(main.go:11)CALLruntime.mapiterinit(SB)0x00bf00191(main.go:11)JMP2070x00c100193(main.go:11)PCDATA$2,$20x00c100193(main.go:11)LEAQ"..autotmp_2+72(SP),AX0x00c600198(main.go:11)PCDATA$2,$00x00c600198(main.go:11)MOVQAX,(SP)0x00ca00202(main.go:11)CALLruntime.mapiternext(SB)0x00cf00207(main.go:11)CMPQ"..autotmp_2+72(SP),$00x00d500213(main.go:11)JNE193...让我们大致看一下整个过程。Gomap循环迭代的重点是两个运行时方法,如下:runtime.mapiterinitruntime.mapiternext但是你可能会想,明明是使用forrangefor循环迭代,这两个函数是怎么出现的,这是怎么回事?看一下转换后的varhitermap_iteration_structformapiterinit(type,range,&hiter);hiter.key!=nil;mapiternext(&hiter){index_temp=*hiter.keyvalue_temp=*hiter.valindex=index_tempvalue=value_temporiginalbody}Actual上述编译器对于slice和map的循环迭代有不同的实现方式。不只是一个forthrowaway,还有一些额外的action进行处理上面的代码是forrangemap经过编译器展开后的伪实现。看源码.bucket.kind&kindNoPointers!=0{h.createOverflow()it.overflow=h.extra.overflowit.oldoverflow=h.extra.oldoverflow}r:=uintptr(fastrand())如果h.B>31-bucketCntBits{r+=uintptr(fastrand())<<31}it.startBucket=r&bucketMask(h.B)it.offset=uint8(r>>h.B&(bucketCnt-1))it.bucket=it.startBucket...mapiternext(it)}通过阅读mapiterinit方法我们可以知道,它的主要作用是在遍历地图时对地图进行初始化。形参一共有三个,分别用来读取当前哈希表的类型信息、当前哈希表的存储信息、当前遍历迭代的数据。为什么要关注源码中的fastrand部分呢?这个方法名是不是很熟悉?.没错,就是一种生成随机数的方法。再次查看上下文:...//决定从哪里开始(h.B)it.offset=uint8(r>>h.B&(bucketCnt-1))//迭代器stateit.bucket=it.startBucket在这段代码中,它生成了随机数。用于决定从哪里开始循环迭代。更具体地说,根据随机数,选择一个桶位置作为遍历迭代的起点。因此,每次重新换范围图,看到的结果都会不同。那是因为它的起始位置根本就不是固定的!runtime.mapiternextfuncmapiternext(it*hiter){...for;我
