Какой порядок обхода у Map в Go
Что проверяют
Знаете ли вы, что порядок обхода map в Go не просто «не гарантирован», а намеренно рандомизирован. Это короткий вопрос, по которому видно, читали ли вы спецификацию или только писали код.
Как отвечать
map в Go это хеш-таблица, и порядок пар в ней не определён. Больше того, при каждом for ... range рантайм начинает обход со случайной позиции: авторы языка сделали это специально, чтобы никто не завязался на порядок, который случайно оказался стабильным на текущей реализации.
m := map[string]int{"apple": 5, "banana": 3, "orange": 4}
for k, v := range m {
fmt.Println(k, v) // порядок разный от запуска к запуску
}Если порядок нужен, его задают явно: собрать ключи в срез, отсортировать, обойти срез.
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Println(k, m[k])
}Полезно добавить два факта рядом. Первый: сериализация в JSON через encoding/json сортирует ключи map, поэтому вывод там стабилен, и это исключение путает людей. Второй: удалять элементы во время обхода можно, а вот добавленные во время обхода элементы могут попасть в текущий проход, а могут и не попасть.
Где спотыкаются
Пишут тест, который сравнивает вывод обхода со строкой. Он зелёный локально и падает в CI через раз.
Считают map упорядоченной структурой по вставке, как в некоторых других языках.
Забывают, что map не безопасна для одновременного доступа: параллельные чтение и запись роняют программу с сообщением о конкурентном доступе, для таких случаев есть мьютекс или sync.Map.