为什么是无序的?
首先,我们先看下go的runtime
中是如何实现map
的迭代,以go 1.21.6为例,以下是关键部分,完整的源码位于src/runtime/map.go中:
func mapiterinit(t *maptype, h *hmap, it *hiter) {
// 省略部分
// decide where to start
var r uintptr
if h.B > 31-bucketCntBits {
r = uintptr(fastrand64())
} else {
r = uintptr(fastrand())
}
it.startBucket = r & bucketMask(h.B)
it.offset = uint8(r >> h.B & (bucketCnt - 1))
// iterator state
it.bucket = it.startBucket
// 省略部分
}
从上面的代码中可以看到,runtime
确定map
迭代的起始位置时使用伪随机数生成器fastrand
和fastrand64
,使用哪个取决于哈希表的位数h.B
,生成一个伪随机数r
,然后再根据r
来确定起始桶和偏移量。
因为每次迭代的起始位置都是不固定的,所以我们每次for range map
的结构可能都是不一样的。
为什么要这样做?
在 Go 语言中,map
的键是无序的主要是为了维护 map
的高效性能和简化实现。以下是一些关于为什么选择无序键的考虑:
1.高效性能:无序键的 map
在插入、查找和删除等操作上具有高效性能。哈希表作为 map
的底层实现,能够提供近似 O(1) 的时间复杂度进行这些操作。无序性可以使哈希表更加灵活,更容易优化和实现。2.简化实现:无序性简化了 map
的实现。无需维护键的顺序,减少了数据结构的复杂性。这对于实现和维护 map
结构是有益的,使得代码更加清晰和高效。3.并发安全:无序键减少了并发访问时需要考虑的因素。在有序键的情况下,为了保持键的顺序,可能需要更复杂的数据结构或更多的同步机制。无序键简化了并发访问的实现。4.避免不确定性:有序键可能会引入不确定性,特别是在哈希表扩容时。在哈希表扩容时,键的顺序可能会发生变化,这可能会导致在遍历 map
时出现意外的结果。无序键可以避免这种不确定性。5.语言规范一致性:Go 语言的语法和规范中并没有规定 map
的键必须有序。因此,无序键符合语言设计的一致性和简洁性。
虽然 map
的键是无序的,但在 Go 1.12 版本及之后,map
的遍历顺序是有序的。这是通过一个有序的哈希表实现的,使得在遍历 map
时能够按照键的插入顺序进行。这种方式在一些应用场景中提供了方便,但在整体设计中仍然保持了 map
键的无序性。
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)[1]进行许可,使用时请注明出处。 Author: mengbin[2] blog: mengbin[3] Github: mengbin92[4] cnblogs: 恋水无意[5] 腾讯云开发者社区:孟斯特[6]
References
[1]
署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0): https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh
[2]
mengbin: mengbin1992@outlook.com
[3]
mengbin: https://mengbin.top
[4]
mengbin92: https://mengbin92.github.io/
[5]
恋水无意: https://www.cnblogs.com/lianshuiwuyi/
[6]
孟斯特: https://cloud.tencent.com/developer/user/6649301