Поиск элемента в map Go — это многоэтапный процесс, оптимизированный для высокой производительности. Рассмотрим его детально, включая внутреннюю механику работы.
hash := hashFunc(key, h.hash0) // h.hash0 - случайное seed для защиты от DoS
Особенности:
hash0
добавляет случайность для защиты от атак коллизиямиbucketIndex := hash & (1<<h.B - 1) // h.B - количество бит для корзин
bucket := h.buckets[bucketIndex]
Особенности:
Каждая корзина содержит:
tophash
(первые 8 бит хешей ключей)for i := 0; i < bucketCnt; i++ {
if bucket.tophash[i] == top {
// Проверяем полное совпадение ключа
if bucket.keys[i] == key {
return bucket.values[i]
}
}
}
Если ключ не найден в основной корзине, проверяются:
if h.oldbuckets != nil {
// Поиск в старых корзинах
}
Рассмотрим, как выглядит поиск на низком уровне (упрощенно):
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
// 1. Вычисление хеша
hash := t.hasher(key, uintptr(h.hash0))
// 2. Нахождение корзины
m := bucketMask(h.B)
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.bucketsize)))
// 3. Поиск в корзине
for ; b != nil; b = b.overflow(t) {
for i := uintptr(0); i < bucketCnt; i++ {
if b.tophash[i] != top {
continue
}
k := add(unsafe.Pointer(b), dataOffset+i*uintptr(t.keysize))
if t.key.equal(key, k) {
// Нашли!
return add(unsafe.Pointer(b), dataOffset+bucketCnt*uintptr(t.keysize)+i*uintptr(t.valuesize))
}
}
}
// 4. Не нашли - возвращаем zero value
return unsafe.Pointer(&zeroVal[0])
}
В среднем случае:
В худшем случае (много коллизий):
// При одновременном чтении и записи:
go func() { m["key"] = "value" }()
go func() { _ = m["key"] }()
// Возможна паника: fatal error: concurrent map read and map write
Решение: Использовать sync.Mutex или sync.Map