管理容量
影响 Vec<T> 的容量的方法有很多种, 接下来我们分别列举主要的几种形式.
Vec 分配的堆内存大小等于 size_of::<T>() * vec.capacity().
new() 函数
这个函数就比较简单了, 它并不会分配堆内存, 容量等于 0.
fn main() {
let numbers = Vec::<i32>::new();
assert_eq!(numbers.len(), 0);
assert_eq!(numbers.capacity(), 0);
}
可以看看标准库中 Vec::new() 的代码实现:
impl<T> Vec<T> {
#[inline]
#[must_use]
pub const fn new() -> Self {
Vec { buf: RawVec::NEW, len: 0 }
}
}
impl<T> RawVec<T> {
/// HACK(Centril): This exists because stable `const fn` can only call stable `const fn`, so
/// they cannot call `Self::new()`.
///
/// If you change `RawVec<T>::new` or dependencies, please take care to not introduce anything
/// that would truly const-call something unstable.
pub const NEW: Self = Self::new();
/// Creates the biggest possible `RawVec` (on the system heap)
/// without allocating. If `T` has positive size, then this makes a
/// `RawVec` with capacity `0`. If `T` is zero-sized, then it makes a
/// `RawVec` with capacity `usize::MAX`. Useful for implementing
/// delayed allocation.
#[must_use]
pub const fn new() -> Self {
Self::new_in()
}
/// Like `new`, but parameterized over the choice of allocator for
/// the returned `RawVec`.
pub const fn new_in() -> Self {
// `cap: 0` means "unallocated". zero-sized types are ignored.
Self { ptr: Unique::dangling(), cap: Cap::ZERO }
}
}
with_capacity(cap) 函数
该函数在创建 vec 的同时, 还给它分配足够多的堆内存, 以便存放 cap 个元素.
vec![] 宏, 以及从迭代器创建
从这两种方法创建 vec 对象时, 都要先获取元素的个数 len, 然后设置新创建的 vec 对象的容量恰好等于 len,
看下面的代码片段:
let v1 = vec![1, 2, 3];
let v2: Vec<i32> = [1, 2, 3].into_iter().collect();
assert_eq!(v1, v2);
assert_eq!(v1.capacity(), 3);
assert_eq!(v2.capacity(), 3);
push() 函数, 向 vec 中加入新元素
以下的代码示例展示了 vec 的扩容策略, 那就是 2 倍扩容, 不管当前的 vec 对象已经占用了多大的内存,
在需要扩容时, 一直都是 2倍扩容.
use std::mem::size_of;
fn main() {
let mut v1 = Vec::<i32>::new();
println!("len of v1: {}, cap: {}", v1.len(), v1.capacity());
v1.push(1);
println!("len of v1: {}, cap: {}", v1.len(), v1.capacity());
v1.push(2);
v1.push(3);
v1.push(4);
v1.push(5);
println!("len of v1: {}, cap: {}", v1.len(), v1.capacity());
let mut v2: Vec<i64> = Vec::new();
println!(
"len of v2: {}, cap: {}, size: {}",
v2.len(),
v2.capacity(),
v2.capacity() * size_of::<i64>()
);
let mut old_cap = v2.capacity();
for i in 0..10_000_000 {
v2.push(i);
if v2.capacity() != old_cap {
old_cap = v2.capacity();
println!(
"len of v2: {}, cap: {}, size: {}",
v2.len(),
v2.capacity(),
v2.capacity() * size_of::<i64>()
);
}
}
}
pop() 函数, 从 vec 中移除元素, 会不会自动释放内存?
先看一下测试代码:
fn main() {
let mut v1: Vec<i32> = Vec::new();
for i in 0..1_000_000 {
v1.push(i);
}
println!("capacity: {}, len: {}", v1.capacity(), v1.len());
while v1.pop().is_some() {
// Ignore
}
println!("capacity: {}, len: {}", v1.capacity(), v1.len());
// 手动释放内存
v1.shrink_to_fit();
println!("shrink_to_fit(), capacity: {}, len: {}", v1.capacity(), v1.len());
}
可以发现, 它并不会自动释放多余的内存, 需要手动调用 resize(), shrink_to_fit() 等函数.
reverse(additional) 函数
这个函数要求数组至少预留 additional 个元数的空间.
fn main() {
let mut v1 = vec![1, 2, 3];
println!("len: {}, cap: {}", v1.len(), v1.capacity());
v1.reserve(12);
println!("len: {}, cap: {}", v1.len(), v1.capacity());
v1.reserve(13);
println!("len: {}, cap: {}", v1.len(), v1.capacity());
}
可以发现, 上面的例子中, 数组多分配了一些空间. 我们看一下标准库中的代码:
fn grow_amortized(&mut self, len: usize, additional: usize) -> Result<(), TryReserveError> {
...
// Nothing we can really do about these checks, sadly.
let required_cap = len.checked_add(additional).ok_or(CapacityOverflow)?;
// This guarantees exponential growth. The doubling cannot overflow
// because `cap <= isize::MAX` and the type of `cap` is `usize`.
let cap = cmp::max(self.cap.0 * 2, required_cap);
let cap = cmp::max(Self::MIN_NON_ZERO_CAP, cap);
let new_layout = Layout::array::<T>(cap);
...
}
如果要留出的空间比当前容量的 2 倍少的话, 会直接使用 2 倍扩容 的策略.
shrink_to_fit() 函数
这个函数比较容易理解, 调整数组的容量, 移除多余的未使用的内存, 这样 len() == capacity().
resize() 函数
调整数组中的元素个数, 如果新的个数比当前的少, 就移除一部分;
如果比当前的个数多, 就添加一部分, 使用指定的值, 同时数组的容量调整, 依然是按照 2 倍扩容 的策略.
fn main() {
let mut v1: Vec<i32> = Vec::new();
v1.resize(42, 1);
println!("len: {}, cap: {}", v1.len(), v1.capacity());
v1.resize(52, 2);
println!("len: {}, cap: {}", v1.len(), v1.capacity());
v1.resize(32, 2);
println!("len: {}, cap: {}", v1.len(), v1.capacity());
}