Vec的内存布局

Vec所存储的数据部分在堆内存中,同时在栈空间中存放了该vec的胖指针。胖指针包括三部分元数据:

  • 指向堆的指针(一个机器字长)
  • 当前vec元素数量(即长度,usize,一个机器字长)
  • vec的容量(即当前vec最多可存放多少元素,usize,一个机器字长)

因此,vec的内存布局大致如下:

vec扩容:重新分配内存

当向vec插入新元素时,如果没有空闲容量,则会重新申请一块内存,大小为原来vec内存大小的两倍(官方手册指明目前Rust并没有确定扩容的策略,以后可能会改变),然后将原vec中的元素拷贝到新内存位置处,同时更新vec的胖指针中的元数据。

例如,有一个容量为10、长度为0的空vec,向该vec中插入前10个元素时不会重新分配内存,但在插入第11个元素时,因容量不够,会重新申请一块内存,容量为20,然后将前10个元素拷贝到新内存位置并将第11个元素放入其中。

通过vec的len()方法可获取该vec当前的元素数量,capacity()方法可获取该vec当前的容量大小。

fn main(){
  let mut v1 = vec![11,22,33];
  // len: 3, cap: 3
  println!("len: {}, cap: {}", v1.len(), v1.capacity());
  
  // push()向vec中插入一个元素,将导致扩容,
  // 扩容将导致重新分配vec的内存
  v1.push(44);
  // len: 4, cap: 6
  println!("len: {}, cap: {}", v1.len(), v1.capacity());
}

显然,当频繁扩容或者当元素数量较多且需要扩容时,大量的内存拷贝会降低程序的性能

因此,如果可以的话,可以采取如下方式:

  • 在创建vec的时候使用Vec::with_capacity()指定一个足够大的容量值,以此来尽量减少可能的内存拷贝。
  • 通过reserve()方法来调整已存在的vec容量,使之至少有指定的空闲容量数,以此来尽量减少可能的内存拷贝。

例如:

fn main(){
  // 创建一个容量为3的空vec
  let mut v1 = Vec::with_capacity(3);
  v1.push(11);
  v1.push(22);
  v1.push(33);
  // len: 3, cap: 3
  println!("len: {}, cap: {}", v1.len(), v1.capacity());

  // 调整v1,使其至少要有10个空闲位置
  v1.reserve(10);
  // len: 3, cap: 13
  println!("len: {}, cap: {}", v1.len(), v1.capacity());
  
  // 当空闲容量足够时,reserve()什么也不做
  v1.reserve(5);
  println!("len: {}, cap: {}", v1.len(), v1.capacity());
}

另外,可以使用shrink_to_fit()方法来释放剩余的容量。一般情况下,不会主动去释放容量。