MYSQL INNODB 中通用雙向連結串列的實現
原創:如果有誤請支援
原始碼在Ut0lst.h中
注意:這裡我將連結串列中的實際的串聯的資料叫做資料類比如:lock_t、mem_block_t
連結串列作為一種的非常重要的資料結構,在任何地方都會用到,這裡簡單解釋一下innodb雙向
連結串列的實現,讓我們來看看innodb連結串列設計的魅力:
經常在某些結構體中看到
UT_LIST_BASE_NODE_T(mem_block_t) base;
UT_LIST_NODE_T(mem_block_t) list;
作為最為基本的一種的資料結構innodb中實現得比較精妙涉及到重要的4個C++知識點:
1、仿函式
2、類成員指標
3、類别範本
4、函式過載
簡單的說仿函式是呼叫的時候類似函式呼叫的形式的類,類成員指標並非一個真正意義上的
指標,而是指向特定類物件相對位置的一個偏移量。
比如如下就是一個仿函式類:
下面是一個簡單的類成員指標使用,他初始化分為2步在最後給出.:
模板和函式過載就沒有什麼好說的了。
接下來我們看看UT_LIST_BASE_NODE_T、UT_LIST_NODE_T分別代表了什麼
實際上他們都是宏定義:
#define UT_LIST_BASE_NODE_T(t) ut_list_base<t, ut_list_node t::*>
#define UT_LIST_NODE_T(t) ut_list_node
那麼他們的型別出來了實際上就是:
ut_list_base和ut_list_node,我們知道在設計連結串列的時候,通常有一個連結串列頭資料結構,用來儲存
比如連結串列中總共多少元素,連結串列頭,連結串列尾等等特徵,但是之前還是想來看看ut_list_node連結串列結構體:
非常簡單沒有包含任何固定的資料資訊,只是包含了連結串列的前後指標,同時包含了一個成員函式reverse,作為
實現連結串列反轉的基礎,這裡也注意到由於沒有包含任何資料資訊成員,做到了連結串列和具體資料類之間的剝離。
在連結串列設計的時候通常有2種方式:
1、連結串列結構包含資料類
2、資料類包含連結串列結構
這裡INNODB使用了後者,讓連結串列的通用更加方便
再來看看ut_list_base 連結串列頭結構體:
這裡再來解釋一下:
在類的內部進行了3種型別的typedef,分別是:
typedef Type elem_type;:具體的型別比如lock_t、mem_block_t。
typedef NodePtr node_ptr; :和模板宣告中的ut_list_node t::* 聯合起來看,實際上他是一個
類成員指標,他指向的會是特定資料類比如lock_t、mem_block_t中特定的一個成員,這個成員一定是
ut_list_node型別的也就是UT_LIST_NODE_T(t)型別的,其中t當然也就是資料類本身,這也是整個
設計中的精髓。
typedef ut_list_node node_type; :和前面的ut_list_node聯合起來看,就能知道
它是一個特定型別的節點型別比如lock_t、mem_block_t。
剩下的就是類成員了:
ulint count;:連結串列中的有多少元素
elem_type* start;:具體資料類元素的起始位置指標
elem_type* end;:具體資料類元素的最後位置指標
node_ptr node;:這裡使用了剛才說的typedef NodePtr node_ptr來定義成員node,那麼我們可以猜測他是指向
特定資料類物件中連結串列結構元素的成員指標,其實的確如此:
如lock_t
剩下還有一個關鍵的仿函式:
這裡解釋一下這個仿函式類:
typedef ut_list_node node_type;和前面的ut_list_node聯合起來看,就能知道
它是一個特定型別的節點型別比如lock_t、mem_block_t。
GenericGetNode(node_type Type::* node) : m_node(node) {} :有參建構函式,透過輸入一個指向特定資料節點的
ut_list_node(UT_LIST_NODE_T(t))成員的值node進行初始化元素m_node。但是這裡只是指向了類成員有了偏移量,但是並沒有初始化記憶體空間,具體的初始化
記憶體空間在實現函式中。
node_type& operator() (Type& elem) :這裡就是仿函式,過載了()運算子,接受一個特定節點型別比如lock_t、mem_block_t
的一個引用輸入,然後返回一個類成員指標的引用,如果是lock_t那麼返回的將是trx_locks,那麼我們就能夠使用它
trx_locks.prev
在連結串列實現中中包含很多方法大概如下:
UT_LIST_INIT:初始化一個連結串列、是一個宏定義
ut_list_prepend:頭插法插入連結串列
ut_list_append:尾插法插入連結串列
ut_list_insert:將某個元素插入到某個元素之後
ut_list_remove:刪除某個節點
ut_list_reverse:連結串列反向
ut_list_move_to_front:將指定的元素放到頭部
好了到這裡我們解釋了關鍵連結串列資料結構下面我們透過一段innodb程式碼來分析一下,這裡我們
我們只是關注連結串列操作所以如下,這裡涉及到初始化和尾插法加入連結串列
UT_LIST_BASE_NODE_T(lock_t) old_locks;
UT_LIST_INIT(old_locks, &lock_t::trx_locks);
lock_t* old_lock = lock_rec_copy(lock, heap);
UT_LIST_ADD_LAST(old_locks, old_lock);
我們來具體解釋一下步驟:
1、UT_LIST_BASE_NODE_T(lock_t) old_locks;定義old_locks為一個連結串列頭物件。
2、UT_LIST_INIT(old_locks, &lock_t::trx_locks);進行初始化,這裡UT_LIST_INIT是一個宏
非常簡單設定全部指標都是NULL,並且初始化node類成員指標指向&lock_t::trx_locks。
3、lock_t* old_lock = lock_rec_copy(lock, heap);我們先不深究這裡面,但是他肯定是一種複製,完成後他返回一個lock_t*的指標
old_lock
4、接下來就是加入節點,這是一個重頭戲,會應用到前面全部的知識。
UT_LIST_ADD_LAST(old_locks, old_lock);
實際上他是一共宏定義
#define UT_LIST_ADD_LAST(LIST, ELEM) ut_list_append(LIST, ELEM)
在經過函式過載呼叫後實際上他會呼叫
然後呼叫:
詳細描述一下:
首先看一下:
template
void
ut_list_append(
List& list,
typename List::elem_type* elem)
{
ut_list_append(
list, elem,
GenericGetNode(list.node));
}
這裡list就是我們初始化的old_locks型別為UT_LIST_BASE_NODE_T(lock_t),elem就是我們copy出來的一個
指向lock_t*的指標old_lock其型別當然也就是UT_LIST_BASE_NODE_T(lock_t)::elem_type*型別實際上就是
lock_t*型別繞了一大圈。
而GenericGetNode(list.node)雖然很長但是我們可以清楚的明白他是
呼叫的建構函式,生成一個匿名物件,typename List::elem_type是用到了ut_list_base定義的型別
elem_type就是一個UT_LIST_BASE_NODE_T(lock_t)::elem_type型別其實就是lock_t型別,而list.node
實際上就是node_ptr型別,初始化已經被定義為&lock_t::trx_locks
接下來由於函式過載的函式呼叫了
ut_list_append(
List& list,
typename List::elem_type* elem,
Functor get_node)
我們來看看。
typename List::node_type& node = get_node(*elem);
將List(old_locks)中的node成員函式指標進行初始化他指向了old_lock這是結構實際連結串列結構的位置。
接下來node.next nodex.prev將是可用的了
node.next = 0;
node.prev = list.end;
將節點的後指標設定為NULL,前指標當然設定為list.end的位置
這裡也看到他確實放到末尾
if (list.end != 0) {
typename List::node_type& base_node = get_node(*list.end);
ut_ad(list.end != elem);
base_node.next = elem;
}
如果連結串列不為空,這裡再次獲得end節點的位置存放到base_node中,
當然也就要將base_node.next設定為我們新加入的節點的指標。
list.end = elem;
將連結串列頭結構的end指標放到我們新插入的elem中。
if (list.start == 0) {
list.start = elem;
}
如果list的start指標為空代表連結串列為空,那麼還需要將start指標指向elem
最後
++list.count;
不解釋了。
從整個連結串列的實現來看仿函式是其中的一個重點,他是一個橋樑其主要分為兩步:
1、初始化指向一個類的成員函式,這是指定他的型別,獲得他的偏移量
2、初始化指向某一個元素,這是獲得他的記憶體空間地址基地址
有了基地址+偏移量就能夠找到實際的元素了。
作者微信:
原始碼在Ut0lst.h中
注意:這裡我將連結串列中的實際的串聯的資料叫做資料類比如:lock_t、mem_block_t
連結串列作為一種的非常重要的資料結構,在任何地方都會用到,這裡簡單解釋一下innodb雙向
連結串列的實現,讓我們來看看innodb連結串列設計的魅力:
經常在某些結構體中看到
UT_LIST_BASE_NODE_T(mem_block_t) base;
UT_LIST_NODE_T(mem_block_t) list;
作為最為基本的一種的資料結構innodb中實現得比較精妙涉及到重要的4個C++知識點:
1、仿函式
2、類成員指標
3、類别範本
4、函式過載
簡單的說仿函式是呼叫的時候類似函式呼叫的形式的類,類成員指標並非一個真正意義上的
指標,而是指向特定類物件相對位置的一個偏移量。
比如如下就是一個仿函式類:
點選(此處)摺疊或開啟
-
template <typename T>
-
class ShowElemt
-
{
-
public:
-
ShowElemt()
-
{
-
n = 0;
-
}
-
void operator()(T &t)
-
{
-
n++;
-
cout << t << " ";
-
}
-
void printCount()
-
{
-
cout << n << endl;
-
}
-
public:
-
int n;
- };
點選(此處)摺疊或開啟
-
#include<iostream>
-
using namespace std;
-
-
class T
-
{
-
public:
-
typedef int uint;
-
public:
-
int a;
-
int b;
-
};
-
-
int main21(void)
-
{
-
T t;
-
int T::* t1 = &T::b;//1、成員函式指標 初始化為指向類T成員b的一個指標(成員函式指標指向的是偏移量)
-
T* t2 = &t;//t2一個指向類變數t的指標
-
t.*t1 = 10;//2、初始化t的t1類成員指標指向的記憶體空間值為10,實際上就是t.b=10
-
cout<<t.*t1<<" "<<t2->*t1<<endl;//相同輸出一個採用物件一個採用物件指標
-
{
-
T t3;
-
t3.a=300;
- t.*t1 = t3.a; //他就是擁有實際記憶體空間的變數了
-
}
- }
接下來我們看看UT_LIST_BASE_NODE_T、UT_LIST_NODE_T分別代表了什麼
實際上他們都是宏定義:
#define UT_LIST_BASE_NODE_T(t) ut_list_base<t, ut_list_node t::*>
#define UT_LIST_NODE_T(t) ut_list_node
那麼他們的型別出來了實際上就是:
ut_list_base和ut_list_node,我們知道在設計連結串列的時候,通常有一個連結串列頭資料結構,用來儲存
比如連結串列中總共多少元素,連結串列頭,連結串列尾等等特徵,但是之前還是想來看看ut_list_node連結串列結構體:
點選(此處)摺疊或開啟
-
template <typename Type>
-
struct ut_list_node {
-
Type* prev; /*!< pointer to the previous
-
node, NULL if start of list */
-
Type* next; /*!< pointer to next node,
-
NULL if end of list */
-
-
void reverse()
-
{
-
Type* tmp = prev;
-
prev = next;
-
next = tmp;
-
}
- };
實現連結串列反轉的基礎,這裡也注意到由於沒有包含任何資料資訊成員,做到了連結串列和具體資料類之間的剝離。
在連結串列設計的時候通常有2種方式:
1、連結串列結構包含資料類
2、資料類包含連結串列結構
這裡INNODB使用了後者,讓連結串列的通用更加方便
再來看看ut_list_base 連結串列頭結構體:
點選(此處)摺疊或開啟
-
template <typename Type, typename NodePtr>
-
struct ut_list_base {
-
typedef Type elem_type;
-
typedef NodePtr node_ptr;
-
typedef ut_list_node<Type> node_type;
-
-
ulint count; /*!< count of nodes in list */
-
elem_type* start; /*!< pointer to list start,
-
NULL if empty */
-
elem_type* end; /*!< pointer to list end,
-
NULL if empty */
-
node_ptr node; /*!< Pointer to member field
-
that is used as a link node */
-
#ifdef UNIV_DEBUG
-
ulint init; /*!< UT_LIST_INITIALISED if
-
the list was initialised with
-
UT_LIST_INIT() */
-
#endif /* UNIV_DEBUG */
-
-
void reverse()
-
{
-
Type* tmp = start;
-
start = end;
-
end = tmp;
-
}
- };
在類的內部進行了3種型別的typedef,分別是:
typedef Type elem_type;:具體的型別比如lock_t、mem_block_t。
typedef NodePtr node_ptr; :和模板宣告中的ut_list_node t::* 聯合起來看,實際上他是一個
類成員指標,他指向的會是特定資料類比如lock_t、mem_block_t中特定的一個成員,這個成員一定是
ut_list_node型別的也就是UT_LIST_NODE_T(t)型別的,其中t當然也就是資料類本身,這也是整個
設計中的精髓。
typedef ut_list_node node_type; :和前面的ut_list_node聯合起來看,就能知道
它是一個特定型別的節點型別比如lock_t、mem_block_t。
剩下的就是類成員了:
ulint count;:連結串列中的有多少元素
elem_type* start;:具體資料類元素的起始位置指標
elem_type* end;:具體資料類元素的最後位置指標
node_ptr node;:這裡使用了剛才說的typedef NodePtr node_ptr來定義成員node,那麼我們可以猜測他是指向
特定資料類物件中連結串列結構元素的成員指標,其實的確如此:
如lock_t
點選(此處)摺疊或開啟
-
/** Lock struct; protected by lock_sys->mutex */
-
struct lock_t {
-
trx_t* trx; /*!< transaction owning the
-
lock */
-
UT_LIST_NODE_T(lock_t)
-
trx_locks; /*!< list of the locks of the
-
transaction */
- ..........
剩下還有一個關鍵的仿函式:
點選(此處)摺疊或開啟
-
template <typename Type> //一元謂詞仿函式
-
struct GenericGetNode {
-
typedef ut_list_node<Type> node_type;
-
GenericGetNode(node_type Type::* node) : m_node(node) {}
-
node_type& operator() (Type& elem)
-
{
-
return(elem.*m_node);
-
}
-
node_type Type::*m_node;
- };
typedef ut_list_node node_type;和前面的ut_list_node聯合起來看,就能知道
它是一個特定型別的節點型別比如lock_t、mem_block_t。
GenericGetNode(node_type Type::* node) : m_node(node) {} :有參建構函式,透過輸入一個指向特定資料節點的
ut_list_node(UT_LIST_NODE_T(t))成員的值node進行初始化元素m_node。但是這裡只是指向了類成員有了偏移量,但是並沒有初始化記憶體空間,具體的初始化
記憶體空間在實現函式中。
node_type& operator() (Type& elem) :這裡就是仿函式,過載了()運算子,接受一個特定節點型別比如lock_t、mem_block_t
的一個引用輸入,然後返回一個類成員指標的引用,如果是lock_t那麼返回的將是trx_locks,那麼我們就能夠使用它
trx_locks.prev
在連結串列實現中中包含很多方法大概如下:
UT_LIST_INIT:初始化一個連結串列、是一個宏定義
ut_list_prepend:頭插法插入連結串列
ut_list_append:尾插法插入連結串列
ut_list_insert:將某個元素插入到某個元素之後
ut_list_remove:刪除某個節點
ut_list_reverse:連結串列反向
ut_list_move_to_front:將指定的元素放到頭部
好了到這裡我們解釋了關鍵連結串列資料結構下面我們透過一段innodb程式碼來分析一下,這裡我們
我們只是關注連結串列操作所以如下,這裡涉及到初始化和尾插法加入連結串列
UT_LIST_BASE_NODE_T(lock_t) old_locks;
UT_LIST_INIT(old_locks, &lock_t::trx_locks);
lock_t* old_lock = lock_rec_copy(lock, heap);
UT_LIST_ADD_LAST(old_locks, old_lock);
我們來具體解釋一下步驟:
1、UT_LIST_BASE_NODE_T(lock_t) old_locks;定義old_locks為一個連結串列頭物件。
2、UT_LIST_INIT(old_locks, &lock_t::trx_locks);進行初始化,這裡UT_LIST_INIT是一個宏
點選(此處)摺疊或開啟
-
#define UT_LIST_INIT(b, pmf) \
-
{ \
-
(b).count = 0; \
-
(b).start = 0; \
-
(b).end = 0; \
-
(b).node = pmf; \
-
UT_LIST_INITIALISE(b); \
- }
非常簡單設定全部指標都是NULL,並且初始化node類成員指標指向&lock_t::trx_locks。
3、lock_t* old_lock = lock_rec_copy(lock, heap);我們先不深究這裡面,但是他肯定是一種複製,完成後他返回一個lock_t*的指標
old_lock
4、接下來就是加入節點,這是一個重頭戲,會應用到前面全部的知識。
UT_LIST_ADD_LAST(old_locks, old_lock);
實際上他是一共宏定義
#define UT_LIST_ADD_LAST(LIST, ELEM) ut_list_append(LIST, ELEM)
在經過函式過載呼叫後實際上他會呼叫
點選(此處)摺疊或開啟
-
template <typename List>
-
void
-
ut_list_append(
-
List& list,
-
typename List::elem_type* elem)
-
{
-
ut_list_append(
-
list, elem,
-
GenericGetNode<typename List::elem_type>(list.node));
- }
然後呼叫:
點選(此處)摺疊或開啟
-
template <typename List, typename Functor>
-
void
-
ut_list_append(
-
List& list,
-
typename List::elem_type* elem,
-
Functor get_node)
-
{
-
typename List::node_type& node = get_node(*elem);
-
-
-
UT_LIST_IS_INITIALISED(list);
-
-
-
node.next = 0;
-
node.prev = list.end;
-
-
-
if (list.end != 0) {
-
typename List::node_type& base_node = get_node(*list.end);
-
-
-
ut_ad(list.end != elem);
-
-
-
base_node.next = elem;
-
}
-
-
-
list.end = elem;
-
-
-
if (list.start == 0) {
-
list.start = elem;
-
}
-
++list.count;
- }
首先看一下:
template
void
ut_list_append(
List& list,
typename List::elem_type* elem)
{
ut_list_append(
list, elem,
GenericGetNode(list.node));
}
這裡list就是我們初始化的old_locks型別為UT_LIST_BASE_NODE_T(lock_t),elem就是我們copy出來的一個
指向lock_t*的指標old_lock其型別當然也就是UT_LIST_BASE_NODE_T(lock_t)::elem_type*型別實際上就是
lock_t*型別繞了一大圈。
而GenericGetNode(list.node)雖然很長但是我們可以清楚的明白他是
呼叫的建構函式,生成一個匿名物件,typename List::elem_type是用到了ut_list_base定義的型別
elem_type就是一個UT_LIST_BASE_NODE_T(lock_t)::elem_type型別其實就是lock_t型別,而list.node
實際上就是node_ptr型別,初始化已經被定義為&lock_t::trx_locks
接下來由於函式過載的函式呼叫了
ut_list_append(
List& list,
typename List::elem_type* elem,
Functor get_node)
我們來看看。
typename List::node_type& node = get_node(*elem);
將List(old_locks)中的node成員函式指標進行初始化他指向了old_lock這是結構實際連結串列結構的位置。
接下來node.next nodex.prev將是可用的了
node.next = 0;
node.prev = list.end;
將節點的後指標設定為NULL,前指標當然設定為list.end的位置
這裡也看到他確實放到末尾
if (list.end != 0) {
typename List::node_type& base_node = get_node(*list.end);
ut_ad(list.end != elem);
base_node.next = elem;
}
如果連結串列不為空,這裡再次獲得end節點的位置存放到base_node中,
當然也就要將base_node.next設定為我們新加入的節點的指標。
list.end = elem;
將連結串列頭結構的end指標放到我們新插入的elem中。
if (list.start == 0) {
list.start = elem;
}
如果list的start指標為空代表連結串列為空,那麼還需要將start指標指向elem
最後
++list.count;
不解釋了。
從整個連結串列的實現來看仿函式是其中的一個重點,他是一個橋樑其主要分為兩步:
1、初始化指向一個類的成員函式,這是指定他的型別,獲得他的偏移量
2、初始化指向某一個元素,這是獲得他的記憶體空間地址基地址
有了基地址+偏移量就能夠找到實際的元素了。
作者微信:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7728585/viewspace-2141547/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- C++實現通用雙向連結串列C++
- 實現雙向連結串列
- Java雙向連結串列的實現Java
- Go實現雙向連結串列Go
- java實現雙向連結串列Java
- 通用雙向連結串列的設計(參考Linux系統中的實現)Linux
- 雙向連結串列的功能實現(初版
- javascript中的連結串列結構—雙向連結串列JavaScript
- 連結串列-雙向連結串列
- 資料結構(雙向連結串列的實現)資料結構
- 雙向連結串列
- Linux核心連結串列-通用連結串列的實現Linux
- JAVA基礎:語言中連結串列和雙向連結串列的實現(轉)Java
- 資料結構-雙向連結串列(Python實現)資料結構Python
- Redis 原始碼解析之通用雙向連結串列(adlist)Redis原始碼
- python 資料結構之雙向連結串列的實現Python資料結構
- 資料結構實驗之連結串列九:雙向連結串列資料結構
- 資料結構--陣列、單向連結串列、雙向連結串列資料結構陣列
- 雙向迴圈連結串列基本操作的實現(C語言)C語言
- go 實現單向連結串列Go
- 資料結構——雙向連結串列資料結構
- 資料結構:雙向連結串列資料結構
- 結構與演算法(03):單向連結串列和雙向連結串列演算法
- C語言之雙向連結串列C語言
- 單向迴圈連結串列的實現
- 雙向連結串列的建立及基本操作
- 資料結構之雙向連結串列資料結構
- DoublyLinkedList(雙向連結串列)——Javascript版JavaScript
- 雙向連結串列 尾節點插入
- 資料結構_連結串列_單向迴圈連結串列 & 雙向連結串列的初始化、插入、刪除、修改、查詢列印(基於C語言實現)資料結構C語言
- C 語言使用非迴圈雙向連結串列實現佇列佇列
- 013 通過連結串列學習Rust之實現連結串列的通用函式Rust函式
- 013 透過連結串列學習Rust之實現連結串列的通用函式Rust函式
- 雙向連結串列的操作(插入和刪除)
- 資料結構與演算法——連結串列 Linked List(單連結串列、雙向連結串列、單向環形連結串列-Josephu 問題)資料結構演算法
- 連結串列-單連結串列實現
- 簡單介紹python中的單向連結串列實現Python
- 畫江湖之資料結構【第一話:連結串列】雙向連結串列資料結構