首页 > 解决方案 > 为什么地图上的迭代是随机的?

问题描述

从 Golang源代码来看,它们似乎遵循了非常标准的哈希表实现(即桶数组)。基于此,对于未更改的映射,迭代似乎应该是确定性的(即按顺序迭代数组,然后按顺序在桶内迭代)。为什么他们使迭代随机?

标签: dictionarygo

解决方案


TL;博士; 他们故意从 Go 1 开始使其随机化,以使开发人员不依赖它(不依赖于特定的迭代顺序,该顺序可能会从发布到发布,从平台到平台,甚至可能在单个当地图内部因容纳更多元素而发生变化时应用程序的运行时)。

Go 博客:运行中的 Go 地图:迭代顺序:

在使用范围循环迭代地图时,未指定迭代顺序,也不保证从一次迭代到下一次迭代顺序相同。自 Go 1.0 发布以来,运行时已随机映射迭代顺序。程序员已经开始依赖 Go 早期版本的稳定迭代顺序,这在实现之间存在差异,从而导致了可移植性错误。如果您需要稳定的迭代顺序,则必须维护一个单独的数据结构来指定该顺序。

Also Go 1 发行说明:在地图中迭代:

旧的语言规范没有定义映射的迭代顺序,实际上它在硬件平台上有所不同。这导致迭代地图的测试变得脆弱且不可移植,具有令人不快的特性,即测试可能总是在一台机器上通过但在另一台机器上中断。

在 Go 1 中,使用 for range 语句在映射上迭代时访问元素的顺序被定义为不可预测,即使同一个循环使用同一个映射多次运行。代码不应假定元素以任何特定顺序被访问。

这种变化意味着依赖于迭代顺序的代码很可能会提前中断并在它成为问题之前很久就被修复。同样重要的是,即使程序使用范围循环从地图中选择元素,它也允许地图实现确保更好的地图平衡。

值得注意的例外

请注意,“随机”顺序适用于使用for range.

对于可重现的输出(为了便于测试和它带来的其他便利),标准库在许多地方对映射键进行排序:

1.encoding/json

json包使用排序键编组映射。引自json.Marshal()

映射值编码为 JSON 对象。映射的键类型必须是字符串、整数类型或实现 encoding.TextMarshaler。通过应用以下规则对映射键进行排序并用作 JSON 对象键,但要遵守上面为字符串值描述的 UTF-8 强制:

  • 直接使用任何字符串类型的键
  • encoding.TextMarshalers 被封送
  • 整数键被转换为字符串

2.fmt包装

Go 1.12开始,该fmt包使用排序键打印地图。引用发行说明:

地图现在以按键排序的顺序打印以简化测试。排序规则如下:

  • 适用时,nil 比较低
  • 整数、浮点数和字符串按 < 排序
  • NaN 比较小于非 NaN 浮点数
  • bool 在 true 之前比较 false
  • 复杂比较实数,然后是虚数
  • 指针按机器地址比较
  • 通道值按机器地址比较
  • 结构体依次比较每个字段
  • 数组依次比较每个元素
  • 接口值首先通过描述具体 > - 类型的 reflect.Type 进行比较,然后通过前面规则中描述的具体值进行比较。

3.转到模板

和包的{{range}}操作也按排序键顺序访问元素。引用包文档:text/templatehtml/templatetext/template

{{range pipeline}} T1 {{end}}
  The value of the pipeline must be an array, slice, map, or channel.
  If the value of the pipeline has length zero, nothing is output;
  otherwise, dot is set to the successive elements of the array,
  slice, or map and T1 is executed. If the value is a map and the
  keys are of basic type with a defined order, the elements will be
  visited in sorted key order.

推荐阅读