1. 程式人生 > >線性表(二) 單鏈表應用——可利用空間表

線性表(二) 單鏈表應用——可利用空間表

常常會有頻繁申請、釋放記憶體的需求,比如在傳送網路報文時,每次都要分配記憶體以儲存報文,等報文傳送完成後又需要刪除報文。 為了避免頻繁的new/delete對系統帶來的開銷,需要實現一個通用的FreeList機制。使用者總是從free list中分配記憶體,如果存在沒有使用的記憶體塊就直接摘出來使用,如果沒有的話再從系統中分配。使用完畢後並不去直接delete該記憶體塊,而是交給FreeList保管。這裡的的Freelist就是我們這篇文章討論的主角:可利用空間表

簡介

可利用空間表 是動態記憶體管理的一種方法。通過把空閒記憶體劃分成固定大小的資料塊,而且利用指標欄位把這些資料塊連結起來,並使用一個指標指向首結點,這樣就形成了一個單鏈表,即可利用空間表(free list)。

如果一個連結串列週期性地增長然後縮短,特別是一個程式使用了多個線性表,可利用空間表是非常有用的:當用戶請求分配時,系統從可利用空間表中刪除一個結點分配之;當用戶釋放其所佔記憶體時,系統即回收並將它插入到可利用空間表中,因此,可利用空間表亦稱為儲存池

可利用空間表有三種結構:結點大小相同結點有若干規格結點大小不等。而今天我們主要介紹最簡單的一種,即結點大小相同的可利用空間表

ps:關於可利用可空間表更詳細的簡介可參考這篇博文可利用空間表(Freelist)

具體實現

可利用空間表的實現要點:

  1. 一個靜態成員指標static FreeList* freelist,用來指向可利用空間表。
  2. 過載 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; }

注:

  1. Link類中的::new表示原來系統的new函式,如果沒有前面的冒號,意味著呼叫了Link類中定義的new操作,從而會導致死迴圈。
  2. 可利用空間表本身被一個稱為freelist的靜態資料成員訪問。如果一個數據成員被宣告為靜態的,則該類中的所有物件共享同一個資料成員。這樣,所有Link物件都可以引用同一個freelist變數。
  3. 一個程式可能需要很多個連結串列。如果它們的元素型別都是相同的,可以共享同一個可利用空間表。如果連結串列建立時的元素資料型別不同,由於原始碼只是一個模板,編譯器在編譯時會發現該類使用不同的資料型別的需求,從而分別為不同的資料型別生成相應版本的Link類,因此不同資料型別的Link類會使用不同的可利用空間表
  4. 如果某個時候程式需要成千上萬個連結串列結點,那麼就需要呼叫很多次系統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~