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()方法來釋放剩餘的容量。一般情況下,不會主動去釋放容量。