为什么是无序的?

首先,我们先看下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迭代的起始位置时使用伪随机数生成器fastrandfastrand64,使用哪个取决于哈希表的位数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)进行许可,使用时请注明出处。
Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特