切片与 Map
slice 和 map 是 Go 最常用的容器,也是考点最密集的区域:底层结构、扩容、共享陷阱、并发安全。
slice:三字段视图
s := make([]int, 3, 5) // len=3, cap=5slice 是“视图”结构:ptr(底层数组指针)+ len + cap。复制 slice 只复制视图,底层数组共享。
扩容机制:
- 追加超过 cap 时扩容,分配新数组并拷贝
- 容量增长:1.18 前小切片双倍,1.18 后小于 256 双倍、之后约 1.25 倍
- 扩容后是新数组:旧 slice 和扩容后的 slice 不再共享
共享陷阱: 视图共享数组, 扩容才分离
经典陷阱:
a := []int{1, 2, 3, 4}
b := a[:2] // b = [1 2], 共享数组
b = append(b, 99) // 有剩余容量: 写的是共享数组! a 变成 [1 2 99 4]面试必答:append 在 cap 内时写共享数组,超 cap 才复制。函数传 slice 修改元素会反映到调用方,但 append 改变 len 不会。
map:hmap 结构
map 底层: 哈希桶 + 溢出链
- hmap:count、桶数组指针、B(桶数 2^B)、扩容状态
- bucket:8 个键值槽 + 溢出桶指针(冲突链)
- 查找:hash(key) → 定位桶 → 槽内比较(tophash 加速)
- 扩容:负载因子超 6.5 或溢出桶过多;渐进式(每次操作搬一部分)
遍历随机性:for range map 的顺序是随机的(语言保证随机起点,防依赖顺序的 bug)。需要有序遍历:排序 key。
并发读写(必考):map 不是并发安全的。并发写或读写并发直接 fatal error: concurrent map writes(不是 panic,不可 recover)。解法:sync.Mutex 包一层、sync.Map(读多写少场景)或分片锁。
面试追问
- slice 扩容规则? 超过 cap 分配新数组拷贝。小切片双倍,大切片 1.25 倍(1.18+)。扩容后不共享
- append 的共享陷阱? cap 内 append 写共享数组,影响所有共享视图。超 cap 才分离
- map 为什么遍历无序? 语言层面随机化,防依赖顺序。要有序自己排序 key
- map 并发写会怎样? fatal error(不可 recover)。加锁或 sync.Map
- map 扩容机制? 负载因子超阈值渐进式扩容:每次操作搬部分桶,避免一次性卡顿