vector的增長機制

// vector_test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <vector>
#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
    //創建一個int型的vector
    vector<int> ivec;
    //設定初始容量
    ivec.reserve(355);
    //打印ivec中元素的個數及容量
    cout<<"ivec: size: "<<ivec.size()<<" capacity: "<<ivec.capacity()<<endl;

    for(int ix=0; ix<365; ++ix){//在ivec中添加365個元素
        if (ivec.capacity()==ivec.size())
            // 按照每100個元素增長重新分配內存,可以根據具體情況定義,當然也可以採用默認的機制
            ivec.reserve(ivec.size() + 100);
        ivec.push_back(ix);
        //打印ivec中元素的個數及容量
        cout<<"ivec: size: "<<ivec.size()<<" capacity: "<<ivec.capacity()<<"==>"<<ivec[ix]<<endl;
    }

    //打印其中的數據
    for(int iy=0; iy<ivec.size(); ++iy){    
        cout<<"  "<<ivec[iy];
    }



    return 0;
}

===========================================================

附一篇不錯的文章:

假設我們希望從一個文件中將一串類型爲double的值讀進一個數據結構中,從而允許我們高效地訪問這些值,通常的方法如下:
   
vector<double> values;
double x;
while (cin >> x)
  values.push_back(x); 
 
當循環結束時,values會容納有所有的值,我們將可以通過values高效地訪問任何值。  

在直覺上,標準庫vector類就像一個內建數組:我們可以認爲它在單塊連續的內存中容納其元素。實際上,儘管C++標準沒有明確要求vector的元素要佔用連續的內存,然而標準委員會在2000年10月份的會議上裁定此項要求的遺漏歸因於工作上的疏忽,並且投票表決將其作爲技術勘誤的一部分而包含進來。這個遲到的要求談不上是多大的痛苦,因爲每一個現有的vector實現本來就是以這種方式工作的。
  
如果一個vector的元素位於連續的內存中,我們就很容易明白它是如何高效地訪問個體元素的 —只要使用與內建數組相同的機制就可以了。不過,要弄明白一個vector實現是如何處理高效增長的問題就不是這麼簡單了,因爲這種增長將不可避免地涉及到將元素從一塊內存區域拷貝到另外一塊內存區域。儘管現代處理器通常特別擅長於將一塊連續的數據從內存的一個地方拷貝到另一個地方,然而這樣的拷貝並非是免費的午餐。因此,思考一個標準庫實現可能是如何處理vector的增長而又不消耗過量的時間或空間,很有意義。
  
本文的餘下部分將討論一個用於管理vector增長的簡單而高效的策略。
  
大小和容量   

要想搞清楚vector類的工作機制,首先要清楚它並不僅僅是一塊內存。相反,每一個vector都關聯有兩個“尺寸”:一個稱爲大小(size),表示vector容納的元素的數量;另一個稱爲容量(capacity),表示可被用來存儲元素的內存總量。比方說,假如v是一個vector,那麼v.size()和v.capacity()則分別返回v的 大小和容量。你可以想象一個vector看起來如下:   

當然了,在vector尾部留有額外的內存的用意在於,當使用push_back向vector追加元素時無需分配更多的內存。如果鄰接於vector尾部的內存當時恰好未被佔用,那麼vector的增長只要將那塊內存合並過來即可。然而這樣的好運氣極其罕見,大多數情況下需要分配新的內存,然後將vector現有的元素拷貝到那塊內存中,然後銷燬原來的元素,最後歸還元素先前佔用的內存。在vector中留有額外的內存的好處在於,這樣的重新分配(代價可能很昂貴)不會每當試圖向vector追加一個元素時都發生。  

重新分配內存的代價有多高昂?它涉及如下四個步驟:
  
爲需要的新容量分配足夠的內存;
將元素從原來的內存拷貝到新內存中;
銷燬原來的內存中的元素;
歸還原來的內存。   

如果元素的數目爲n,那麼我們知道步驟(2)和(3)都要佔用O(n)的時間,除非分配或歸還內存的代價的增長超過O(n),否則這兩步將在全部運行時間中佔居支配地位。因此我們可以得出結論:無論用於重新分配的容量(capacity)是多少,重新分配一個大小(size)爲n的vector需要佔用O(n)的時間。   

這個結論暗示了一種折衷權衡。假如在重新分配時請求大量的額外內存,那麼在相當長的時間內將無需再次進行重新分配,因此總體重新分配操作消耗的時間相對較少,這種策略的代價在於將會浪費大量的空間。另一方面,我們可以只請求一點點額外的內存,這麼做將會節約空間,但後繼的重新分配操作將會耗費時間。換句話說,我們面臨一個經典的抉擇:拿時間換空間,或者相反。

重新分配策略   

作爲一個極端的例子,假定每當填充vector一次我們就將其容量增加1個單位,這種策略耗費儘可能少的內存空間,但每當追加一個元素時都要重新分配整個vector。我們說過,重新分配一個具有n個元素的vector佔用O(n)的時間,因此,如果我們從一個空vector開始並將其增長到k個元素,那麼佔用的總時間將會是O(1+2+...+k)或者O(k2),這太可怕了!有沒有更好的辦法呢?  

比方說,假如不是以步幅1來增長vector的容量,而是以一個常量C的步幅來增長它將會如何?很明顯這個策略將會減少重新分配的次數(基於因子C),所以這當然是一種改進,但這個改進到底有多大呢?
  
理解這個改進的方式之一是要認識到此一新策略將針對每C個元素塊進行一次重新分配。假設我們爲總量爲KxC個元素分配K塊內存,那麼,第一次重新分配將會拷貝C個元素,第二次將會拷貝2xC個元素,等等。Big-O表示法不考慮常量因子,因此我們可以將所有的C因子分攤開來而獲得O(1+2+...+K)或者O(K2)的總時間。換句話說,時間仍然是元素個數的二次方程,不過是帶有一個小得多的因子罷了。
  
撇開較小的因子不談,“二次行爲”仍然太糟糕,即使有一個快速的處理器也是如此。實際上,對於快速的處理器來說尤其糟糕,因爲快速的處理器通常伴有大量的內存,而訪問具有大量內存的快速處理器的程序員常常試圖用盡那些內存(這是遲早的事)。這些程序員往往會發現,如果在運行一個二次算法的話,處理器的速度於事無補。  

我們剛剛證實,一個希望能以小於“二次時間”而分配大型vector的實現是不能使用“每次填充時以常量步幅增長vector容量”的策略的,相反,被分配的附加內存的數量必須隨着vector的增長而增長。這個事實暗示存在一種簡單的策略:vector從單個元素開始而後每當重新分配時倍增其容量,如何?事實證明這種策略允許我們以O(n)的時間構建一個有着n個元素的vector。 
 
爲了理解是如何獲得這樣的效率的,考慮當我們已經完全填滿它並打算對其重新分配時的vector的狀態:   

自最近一次重新分配內存以來被追加到vector中的元素有一半從未被拷貝過,而對於那些被拷貝的元素而言,其中一半隻被拷貝了一次,其餘的一半被拷貝了兩次,以此類推。 
 
換句話說,有n/2的元素被拷貝了一次或多次,有n/4的元素被拷貝了兩次或多次,等等。因此,拷貝元素的總數目爲n/2 + n/4+...,結果可以近似爲n(隨着n的增大,這個近似值越發精確)。撇開拷貝動作不談,有n個元素被追加到了vector中,但操作佔用的時間總量仍然是O(n)而不是O(n2)。   

討論   

C++標準並沒有規定vector類必須以某種特定的方式管理其內存,它只是要求通過重複調用push_back而創建一個具有n個元素的vector耗費的時間不得超過O(n),我們剛纔討論的策略可能是滿足此項要求的最直截了當的一種。
   
因爲對於這樣的操作來說vector具有優秀的時間性能,所以沒有什麼理由避免使用如下循環:
   
vector<double> values;
double x;
while (cin >> x)
  values.push_back(x);   

是的,當其增長時,實現將會重新分配vector的元素,但是,如果我們事先能夠預測vector最終 大小的話,這個重新分配耗費的時間將不會超過“一個常量因子”可能會佔用的時間。
   
練習   

1.設想我們通過以如下方式編寫代碼而努力使我們那個小型循環速度更快:
   
while (cin >> x)
{
  if (values.size() == values.capacity())
      values.reserve(values.size() + 1000);
  values.push_back(x);
}

效果將會如何?成員函數reserve進行一次重新分配,從而改變vector的capacity,使其大於或等於其參數。 
 
2.設想不是每次倍增vector的大小,而是增大三倍,在性能上將會產生什麼樣的影響?特別是,創建一個具有n個元素的vector的運行時間仍然爲O(n)嗎? 
 
3.設想你知道你的vector最終將擁有多少元素,在這種情況下,在填充元素之前你可以調用reserve來預先分配數量合適的內存。試一試你手 頭的vector實現,看看調用reserve與否對你的程序的運行時間有多大的影響。

~~The END~~
尹曙光
發佈了31 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章