Почему range итерируется по map по разному
myMap := make(map[int]string)
myMap[0]="dog"
myMap[1] = "cat"
for _,val := range myMap{
fmt.Println(val)
}
почему вывод при запусках может быть разный? Насколько мне известно, несортированная мапа работат при помощи хеша и берёт хеш по ключу и полученное значение это приращение к адресу начала памяти мапы, и по адресу мапы+приращение кладётся информация. Тогда, если функция взятия хеша одна и та же, то и вывод должен быть один и тот же, пусть не отсортированный по ключу(т.к. хеш от 0 может быть и больше чем хеш от 1), но тем не менее. Объясните почему не происходит как я ожидаю, а происходит так, что вывод может отличаться, при том же коде
Заранее спасибо
Ответы (3 шт):
The iteration order over maps is not specified and is not guaranteed to be the same from one iteration to the next.
Всё строго в соответствии с документацией.
Это сделано специально, чтобы программист не опирался на определённый порядок итерации. Речь не идёт об изменении чего-то внутри мапы, сама итерация работает по разному на одной и той-же неизменной мапе:
package main
import "fmt"
func main() {
myMap := make(map[int]int)
for i := 0; i < 10; i++ {
myMap[i] = i
}
for i := 0; i < 10; i++ {
for _, val := range myMap {
fmt.Print(" ", val)
}
fmt.Println()
}
}
$ go run temp.go 0 4 6 8 1 2 3 5 7 9 0 4 6 8 7 9 1 2 3 5 0 4 6 8 9 1 2 3 5 7 3 5 7 9 1 2 6 8 0 4 7 9 1 2 3 5 0 4 6 8 2 3 5 7 9 1 4 6 8 0 0 4 6 8 9 1 2 3 5 7 1 2 3 5 7 9 0 4 6 8 0 4 6 8 7 9 1 2 3 5 9 1 2 3 5 7 0 4 6 8
P.S. Это спорное решение. Обычно программист предполагает что программа работает детерминировано. Я исключаю тут специальные средства вроде инициализации генератора случайных чисел текущим временем, или конкуренцию за ресурсы в многопоточной программе. Если вы пишите обычное вычисление, то вы привыкли думать что оно будет воспроизводиться раз за разом. Детерминированость предполагается для всех основных средств языка, это удобно.
В Go для мапы сделано наоборот. Итерация по мапе не детерминирована. Это решение может попортить кровь, если вы про него не знали. Но теперь вы знаете, и знаете что нужно вычитывать документацию по языку и стандартной библиотеке до последнего слова.
При инициализации итератора используется случайное число (runtime/map.go
):
// decide where to start
r := uintptr(rand())
it.startBucket = r & bucketMask(h.B)
Здесь it
- указатель на итератор, h
- хэш-таблица, startBucket
- корзина хэш-таблицы, с которой начинается итерация.