Vec<T> 是一个动态的、可增长的数组,存储元素在一个单一分配的(在堆上)内存块中。添加新元素(push)会增加长度,必要时会分配新的内存(重新分配)。在 push 过程中,为了避免频繁的重新分配,容量会以指数方式增长。删除元素(pop/remove)时,容量不会自动减小。
一个常见的问题是不断添加时的过度分配和重新分配。
预先分配的工作示例:
let mut v = Vec::with_capacity(1000); for i in 0..1000 { v.push(i); } assert_eq!(v.capacity(), 1000);
问题: 调用 v.shrink_to_fit() 后,vec 的容量会发生什么?会和长度完全相等吗?
错误答案: 是的,总是,在 shrink_to_fit 后容量 == 长度。
正确答案: 不一定,shrink_to_fit 的实现是对分配器的一个“建议”。通常会趋向于尽可能小的容量,可能会根据实现分配器的不同而有所不同(例如,可能会高于长度)。
示例:
let mut v = Vec::with_capacity(10); for i in 0..5 { v.push(i); } v.shrink_to_fit(); // 容量 ≥ 长度 (5),但不保证等于长度
故事
开发者多次向 Vec 中推送对象而未设置容量,这导致在大量数据时的指数级重新分配,阻碍了在“重”循环中的整处理。通过使用
with_capacity优化,时间减少了 10 倍。
故事
团队试图通过在每次 pop() 后定期调用
shrink_to_fit来节省内存,结果导致了不断的重新分配/释放循环,性能下降,几乎将服务推向 DoS。
故事
将 Vec 作为结构体的字段,内部引用其元素,推送超过容量后发生的重新分配使引用失效 — 产生的错误在生产环境中难以追踪。