線性表(二) 單鏈表應用——可利用空間表
常常會有頻繁申請、釋放記憶體的需求,比如在傳送網路報文時,每次都要分配記憶體以儲存報文,等報文傳送完成後又需要刪除報文。 為了避免頻繁的new/delete對系統帶來的開銷,需要實現一個通用的FreeList機制。使用者總是從free list中分配記憶體,如果存在沒有使用的記憶體塊就直接摘出來使用,如果沒有的話再從系統中分配。使用完畢後並不去直接delete該記憶體塊,而是交給FreeList保管。這裡的的Freelist就是我們這篇文章討論的主角:可利用空間表。
簡介
可利用空間表 是動態記憶體管理的一種方法。通過把空閒記憶體劃分成固定大小的資料塊,而且利用指標欄位把這些資料塊連結起來,並使用一個指標指向首結點,這樣就形成了一個單鏈表,即可利用空間表(free list)。
如果一個連結串列週期性地增長然後縮短,特別是一個程式使用了多個線性表,可利用空間表是非常有用的:當用戶請求分配時,系統從可利用空間表中刪除一個結點分配之;當用戶釋放其所佔記憶體時,系統即回收並將它插入到可利用空間表中,因此,可利用空間表亦稱為儲存池。
可利用空間表有三種結構:結點大小相同
、結點有若干規格
、結點大小不等
。而今天我們主要介紹最簡單的一種,即結點大小相同的可利用空間表。
ps:關於可利用可空間表更詳細的簡介可參考這篇博文可利用空間表(Freelist)
具體實現
可利用空間表的實現要點:
- 一個靜態成員指標static FreeList* freelist,用來指向可利用空間表。
- 過載 new 和 delete。
還記得我們的老朋友Link類嗎?
template <typename E> class Link
{
public:
E element;
Link *next;
Link(const E& elemval, Link* nextval = NULL)
{ element = elemval; next = nextval; }
Link(Link* nextval = NULL)
{ next = nextval; }
}
實現我們的可利用空間表只需要對Link類做些修改即可
template <typename E> class Link
{
private:
static Link<E>* freelist;
public:
E element;
Link *next;
Link(const E& elemval, Link* nextval = NULL);
Link(Link* nextval = NULL);
void* operator new(size_t);
void operator delete(void* ptr);
}
template <typename E>
Link<E>* Link<E>::freelist = NULL; //靜態資料成員freelist初始化
template <typename E>
Link<E>::Link(const E& elemval, Link* nextval = NULL)
{
this->element = elemval;
this->next = nextval;
}
template <typename E>
Link<E>::Link( Link* nextval = NULL)
{
this->next = nextval;
}
template <typename E>
void* Link<E>::operator new(size_t);
{
/*freelist沒有可用空間,就從系統分配*/
if(freelist == NULL) return ::new Link;
/*否則,從freelist表頭摘取結點*/
Link<E>* temp = freelist;
freelist = freelist->next;
return temp;
}
template <typename E>
void Link::operator delete(void* ptr)
{
/*把要釋放的結點空間加入到freelist中*/
((Link<E>*)ptr)->next = freelist;
freelist = (Link<E>*)ptr;
}
注:
- Link類中的
::new
表示原來系統的new
函式,如果沒有前面的冒號,意味著呼叫了Link類中定義的new
操作,從而會導致死迴圈。 - 可利用空間表本身被一個稱為freelist的靜態資料成員訪問。如果一個數據成員被宣告為靜態的,則該
類中的所有物件共享
同一個資料成員。這樣,所有Link物件都可以引用同一個freelist變數。 - 一個程式可能需要很多個連結串列。如果它們的元素型別都是相同的,可以共享同一個可利用空間表。如果連結串列建立時的元素資料型別不同,由於原始碼只是一個模板,編譯器在編譯時會發現該類使用不同的資料型別的需求,從而分別為不同的資料型別生成相應版本的Link類,因此
不同資料型別的Link類會使用不同的可利用空間表
。 - 如果某個時候程式需要成千上萬個連結串列結點,那麼就需要呼叫很多次系統new操作符來建立它們。採用可利用空間表,對此問題有額外功效:可以採用呼叫一次系統new操作符時就分配很多個連結串列結點的方法,以避免需要更多的結點時可利用空間表馬上就空了的情況發生。
針對4的具體實現,博主探討性的給出下面的示例:
呼叫一次new操作獲得100個結點的空間,遠比呼叫100次new操作每次獲得1個結點的空間要快。我們可以通過ptr = ::new Link[100]
使ptr指向含有100個連結串列結點的陣列,在Link類中過載的new操作符可以把這100個結點作為一個普通連結串列放在可利用空間表中。
template <typename E>
void* Link<E>::operator new(size_t)
{
if(freelist == NULL)
{
/*開闢一塊連續空間並將其組織成連結串列*/
Link<E>* ptr = ::new Link[size_t];
for(int i = 0; i < size_t-1; i++)
ptr[i]->next = ptr[i+1];
ptr[size_t-1]->next = NULL;
freelist = ptr;
}
Link<E>* temp = freelist;
freelist = freelist->next;
return temp;
}
ps:關於非固定模式的分配和回收機制的通用儲存管理器,博主將會在以後的文章中介紹,更新ing~