如何避免vector容器進行不必要的擴容?

2020-07-16 10:05:25
前面提到,我們可以將 vector 容器看做是一個動態陣列。換句話說,在不超出 vector 最大容量限制(max_size() 成員方法的返回值)的前提下,該型別容器可以自行擴充容量來滿足使用者儲存更多元素的需求。

值得一提的是,vector 容器擴容的整個過程,和 realloc() 函數的實現方法類似,大致分為以下 4 個步驟:
  1. 分配一塊大小是當前 vector 容量幾倍的新儲存空間。注意,多數 STL 版本中的 vector 容器,其容器都會以 2 的倍數增長,也就是說,每次 vector 容器擴容,它們的容量都會提高到之前的 2 倍;
  2. 將 vector 容器儲存的所有元素,依照原有次序從舊的儲存空間複製到新的儲存空間中;
  3. 解構掉舊儲存空間中儲存的所有元素;
  4. 釋放舊的儲存空間。

通過以上分析不難看出,vector 容器的擴容過程是非常耗時的,並且當容器進行擴容後,之前和該容器相關的所有指標、疊代器以及參照都會失效。因此在使用 vector 容器過程中,我們應盡量避免執行不必要的擴容操作。

要實現這個目標,可以借助 vector 模板類中提供的 reserve() 成員方法。不過在講解如何用 reserve() 方法避免 vector 容器進行不必要的擴容操作之前,vector 模板類中還提供有幾個和 reserve() 功能類似的成員方法,很容易混淆,這裡有必要為讀者梳理一下,如表 1 所示。

表 1 vector模板類中功能類似的成員方法
成員方法 功能
size() 告訴我們當前 vector 容器中已經存有多少個元素,但僅通過此方法,無法得知 vector 容器有多少儲存空間。
capacity() 告訴我們當前 vector 容器總共可以容納多少個元素。如果想知道當前 vector 容器有多少未被使用的儲存空間,可以通過 capacity()-size() 得知。注意,如果 size() 和 capacity() 返回的值相同,則表明當前 vector 容器中沒有可用儲存空間了,這意味著,下一次向 vector 容器中新增新元素,將導致 vector 容器擴容。
resize(n) 強制 vector 容器必須儲存 n 個元素,注意,如果 n 比 size() 的返回值小,則容器尾部多出的元素將會被解構(刪除);如果 n 比 size() 大,則 vector 會藉助預設建構函式建立出更多的預設值元素,並將它們儲存到容器末尾;如果 n 比 capacity() 的返回值還要大,則 vector 會先擴增,在新增一些預設值元素。
reserve(n) 強制 vector 容器的容量至少為 n。注意,如果 n 比當前 vector 容器的容量小,則該方法什麼也不會做;反之如果 n 比當前 vector 容器的容量大,則 vector 容器就會擴容。

通過對以上幾個成員方法功能的分析,我們可以總結出一點,即只要有新元素要新增到 vector 容器中而恰好此時 vector 容器的容量不足時,該容器就會自動擴容。

因此,避免 vector 容器執行不必要的擴容操作的關鍵在於,在使用 vector 容器初期,就要將其容量設為足夠大的值。換句話說,在 vector 容器剛剛構造出來的那一刻,就應該借助 reserve() 成員方法為其擴充足夠大的容量。

舉個例子,假設我們想建立一個包含 1~1000 的 vector<int>,通常會這樣實現:
vector<int>myvector;
for (int i = 1; i <= 1000; i++) {
    myvector.push_back(i);
}
值得一提的是,上面程式碼的整個迴圈過程中,vector 容器會進行 2~10 次自動擴容(多數的 STL 標準庫版本中,vector 容器通常會擴容至當前容量的 2 倍,而這裡 1000≈2 10),程式的執行效率可想而知。

在上面程式的基礎上,下面程式碼演示了如何使用 reserve() 成員方法盡量避免 vector 容器執行不必要的擴容操作:
vector<int>myvector;
myvector.reserve(1000);
cout << myvector.capacity();
for (int i = 1; i <= 1000; i++) {
    myvector.push_back(i);
}
相比前面的程式碼實現,整段程式在執行過程中,vector 容器的容量僅擴充了 1 次,執行效率大大提高。

當然在實際場景中,我們可能並不知道 vector 容器到底要儲存多少個元素。這種情況下,可以先預留出足夠大的空間,當所有元素都儲存到 vector 容器中之後,再去除多餘的容量。

關於怎樣去除 vector 容器多餘的容量,可以借助該容器模板類提供的 shrink_to_fit() 成員方法,另外後續還會講解如何使用 swap() 成員方法去除 vector 容器多餘的容量,兩種方法都可以。