Как работает итерирование по map в Go и почему порядок не гарантирован?

Ответ

Итерирование по map в Go осуществляется с помощью конструкции for ... range. Ключевая особенность заключается в том, что порядок итерации не гарантирован и является случайным.

Почему порядок случайный?
Это осознанное решение разработчиков языка. Внутренне map реализована как хеш-таблица. Порядок элементов в ней зависит от их хешей и внутренней структуры бакетов. При изменении размера map (добавлении элементов) может произойти перехеширование, и внутренний порядок элементов изменится. Чтобы разработчики не писали хрупкий код, который по ошибке зависит от порядка итерации, в Go намеренно рандомизируют стартовую точку обхода map при каждом новом цикле for ... range.

Пример итерации:

package main

import "fmt"

func main() {
    m := map[string]int{
        "alpha": 1,
        "beta":  2,
        "gamma": 3,
    }

    // При каждом запуске программы порядок вывода может быть разным
    for key, value := range m {
        fmt.Printf("Key: %s, Value: %dn", key, value)
    }
}

Как получить отсортированный результат?
Если требуется детерминированный (стабильный) порядок обхода, необходимо сначала получить все ключи, отсортировать их, а затем итерироваться по отсортированному срезу ключей, получая значения из map.

package main

import (
    "fmt"
    "sort"
)

func main() {
    m := map[string]int{
        "gamma": 3,
        "alpha": 1,
        "beta":  2,
    }

    // 1. Создаем срез для ключей
    keys := make([]string, 0, len(m))
    for k := range m {
        keys = append(keys, k)
    }

    // 2. Сортируем ключи
    sort.Strings(keys)

    // 3. Итерируемся по отсортированным ключам
    for _, k := range keys {
        fmt.Printf("Key: %s, Value: %dn", k, m[k])
    }
}