管理容量

影响 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() 的代码实现:

#![allow(unused)]
fn main() {
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, 看下面的代码片段:

#![allow(unused)]
fn main() {
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());
}

可以发现, 上面的例子中, 数组多分配了一些空间. 我们看一下标准库中的代码:

#![allow(unused)]
fn main() {
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());
}