直接回答(Go 1.23 及之前的经典实现):map 由 hmap 头和桶数组 bmap 组成,hmap 记录 count、桶数对数 B、桶指针等。每桶固定 8 个槽位,桶顶 8 字节 tophash 数组存 key 哈希的高 8 位用于快速过滤;桶内 key、value 各自连续摆放以减少填充;装不下挂溢出桶成链表。定位:哈希低 B 位选桶,tophash 筛掉大部分不匹配,再比较完整 key。
扩容机制:触发条件有二——负载因子(count / 2^B)超 6.5 时翻倍扩容;溢出桶过多但负载不高时做等量扩容,重排回收溢出桶。扩容不是一次完成:新桶分配后旧桶挂 oldbuckets,之后每次写操作顺带搬迁 1~2 个桶(evacuate),直到搬完。搬迁期间读要查新旧两处,这避免了长时间停顿,也解释了扩容中遍历顺序更乱。删除 key 只清标记不立即释放桶——长期增删的大 map 会有空桶占用,必要时重建。补充:Go 1.24 起默认实现已切换为 Swiss Table(8 槽 group、控制字节 SIMD 探测、目录式扩容),能指出这次演进是加分项。
追问方向:为什么 map 遍历顺序随机?为什么不能取 map 元素的地址?Swiss Table 好在哪?(约 557 字)