class="primary">
Redis源碼剖析--雙端連結清單sdlist
- sdlist的資料結構
- sdlist疊代器結構
- sdlist基本操作
- sdlist建立
- sdlist釋放
- 插入節點
- 向頭部插入節點
- 向尾部添加節點
- 向任意位置插入節點
- 删除節點
- 疊代器相關操作
- 擷取疊代器
- 釋放疊代器
- 重置疊代器
- 擷取下一個疊代器
- 連結清單複制函數
- 查找函數
- 連結清單旋轉函數
- sdlist小結
今天來分析Redis的一個基本資料結構–雙端連結清單,其定義和實作主要在sdlist.h和sdlist.c檔案中。其主要用在實作清單鍵、事務子產品儲存輸入指令和伺服器子產品,訂閱子產品儲存多個用戶端等。
sdlist的資料結構
Redis為雙端連結清單的每一個節點定義了如下的結構體。
// 連結清單節點定義
typedef struct listNode {
struct listNode *prev; // 指向前一個節點
struct listNode *next; // 指向後一個節點
void *value; // 節點值
} listNode;
與一般的雙端連結清單無異,定義了連結清單節點的結構體之後,下面就定義連結清單的結構體,用來友善管理連結清單節點,其結構體定義如下:
typedef struct list {
listNode *head; // 指向連結清單頭節點
listNode *tail; // 指向連結清單尾節點
void *(*dup)(void *ptr); // 自定義節點值複制函數
void (*free)(void *ptr); // 自定義節點值釋放函數
int (*match)(void *ptr, void *key); // 自定義節點值比對函數
unsigned long len; // 連結清單長度
} list;
Redis在實作連結清單的時候,定義其為雙端無環連結清單,其示意圖如下:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiInBnaucDdwwWOwEjawMjaiFzM3R2YntmYmFzdnlzNj5Ed2ADMvwVZnJXYs9CXuNmLn1Wah5Waz5iM3d3Lc9CX6MHc0RHaiojIsJye.jpg)
此外,Redis對其結構體提供了一系列的宏定義函數,友善操作其結構體參數
#define listLength(l) ((l)->len) // 擷取list長度
#define listFirst(l) ((l)->head) // 擷取list頭節點指針
#define listLast(l) ((l)->tail) // 擷取list尾節點指針
#define listPrevNode(n) ((n)->prev) // 擷取目前節點前一個節點
#define listNextNode(n) ((n)->next) // 擷取目前節點後一個節點
#define listNodeValue(n) ((n)->value) // 擷取目前節點的值
#define listSetDupMethod(l,m) ((l)->dup = (m)) // 設定節點值複制函數
#define listSetFreeMethod(l,m) ((l)->free = (m)) // 設定節點值釋放函數
#define listSetMatchMethod(l,m) ((l)->match = (m)) // 設定節點值比對函數
#define listGetDupMethod(l) ((l)->dup) // 擷取節點值指派函數
#define listGetFree(l) ((l)->free) // 擷取節點值釋放函數
#define listGetMatchMethod(l) ((l)->match) // 擷取節點值比對函數
sdlist疊代器結構
Redis為sdlist定義了一個疊代器結構,其能正序和逆序的通路list結構。
typedef struct listIter {
listNode *next; // 指向下一個節點
int direction; // 方向參數,正序和逆序
} listIter;
對于direction參數,Redis提供了兩個宏定義
#define AL_START_HEAD 0 // 從頭到尾
#define AL_START_TAIL 1 // 從尾到頭
sdlist基本操作
sdlist建立
sdlist提供了listCreate函數來建立一個空的連結清單。
list *listCreate(void)
{
struct list *list; // 定義一個連結清單指針
if ((list = zmalloc(sizeof(*list))) == NULL) // 申請記憶體
return NULL;
list->head = list->tail = NULL; // 空連結清單的頭指針和尾指針均為空
list->len = 0; // 設定長度
list->dup = NULL; // 自定義複制函數初始化
list->free = NULL; // 自定義釋放函數初始化
list->match = NULL; // 自定義比對函數初始化
return list;
}
sdlist釋放
sdlist提供了listRelease函數來釋放整個連結清單
void listRelease(list *list)
{
unsigned long len;
listNode *current, *next;
current <span style="color:#f92672">=</span> list<span style="color:#f92672">-></span>head;
len <span style="color:#f92672">=</span> list<span style="color:#f92672">-></span>len;
<span style="color:#66d9ef">while</span>(len<span style="color:#f92672">--</span>) {
next <span style="color:#f92672">=</span> current<span style="color:#f92672">-></span>next;
<span style="color:#75715e">// 如果定義了節點值釋放函數,需要調用
if (list->free) list->free(current->value);
zfree(current); // 釋放目前節點
current = next;
}
zfree(list); // 釋放連結清單頭
}
插入節點
sdlist提供了三個函數來完成向list中插入一個節點的功能。
向頭部插入節點
// 該函數向list的頭部插入一個節點
list *listAddNodeHead(list *list, void *value)
{
listNode *node;
<span style="color:#66d9ef">if</span> ((node <span style="color:#f92672">=</span> zmalloc(<span style="color:#66d9ef">sizeof</span>(<span style="color:#f92672">*</span>node))) <span style="color:#f92672">==</span> NULL)
<span style="color:#66d9ef">return</span> NULL;
node<span style="color:#f92672">-></span>value <span style="color:#f92672">=</span> value;
<span style="color:#66d9ef">if</span> (list<span style="color:#f92672">-></span>len <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>) { <span style="color:#75715e">// 如果連結清單為空
list->head = list->tail = node;
node->prev = node->next = NULL;
} else { // 如果連結清單非空
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++; // 長度+1
return list;
}
向尾部添加節點
// 該函數可以在list的尾部添加一個節點
list *listAddNodeTail(list *list, void *value)
{
listNode *node;
<span style="color:#66d9ef">if</span> ((node <span style="color:#f92672">=</span> zmalloc(<span style="color:#66d9ef">sizeof</span>(<span style="color:#f92672">*</span>node))) <span style="color:#f92672">==</span> NULL)
<span style="color:#66d9ef">return</span> NULL;
node<span style="color:#f92672">-></span>value <span style="color:#f92672">=</span> value;
<span style="color:#66d9ef">if</span> (list<span style="color:#f92672">-></span>len <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>) { <span style="color:#75715e">// 如果連結清單為空
list->head = list->tail = node;
node->prev = node->next = NULL;
} else { // 如果連結清單非空
node->prev = list->tail;
node->next = NULL;
list->tail->next = node;
list->tail = node;
}
list->len++; // 長度+1
return list;
}
向任意位置插入節點
// 向任意位置插入節點
// 其中,old_node為插入位置
// value為插入節點的值
// after為0時表示插在old_node前面,為1時表示插在old_node後面
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
listNode *node;
<span style="color:#66d9ef">if</span> ((node <span style="color:#f92672">=</span> zmalloc(<span style="color:#66d9ef">sizeof</span>(<span style="color:#f92672">*</span>node))) <span style="color:#f92672">==</span> NULL)
<span style="color:#66d9ef">return</span> NULL;
node<span style="color:#f92672">-></span>value <span style="color:#f92672">=</span> value;
<span style="color:#66d9ef">if</span> (after) { <span style="color:#75715e">// 向後插入
node->prev = old_node;
node->next = old_node->next;
// 如果old_node為尾節點的話需要改變tail
if (list->tail old_node) {
list->tail = node;
}
} else { // 向前插入
node->next = old_node;
node->prev = old_node->prev;
// 如果old_node為頭節點的話需要改變head
if (list->head old_node) {
list->head = node;
}
}
if (node->prev != NULL) {
node->prev->next = node;
}
if (node->next != NULL) {
node->next->prev = node;
}
list->len++;
return list;
}
删除節點
void listDelNode(list *list, listNode *node)
{
if (node->prev) // 删除節點不為頭節點
node->prev->next = node->next;
else // 删除節點為頭節點需要改變head的指向
list->head = node->next;
if (node->next) // 删除節點不為尾節點
node->next->prev = node->prev;
else // 删除節點為尾節點需要改變tail的指向
list->tail = node->prev;
if (list->free) list->free(node->value); // 釋放節點值
zfree(node); // 釋放節點
list->len--;
}
疊代器相關操作
sdlist為其疊代器提供了一些操作,用來完成擷取疊代器,釋放疊代器,重置疊代器,擷取下一個疊代器等操作,具體源碼見如下分析。
擷取疊代器
listIter *listGetIterator(list *list, int direction)
{
listIter *iter; // 聲明疊代器
if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
// 根據疊代方向來初始化iter
if (direction == AL_START_HEAD)
iter->next = list->head;
else
iter->next = list->tail;
iter->direction = direction;
return iter;
}
釋放疊代器
void listReleaseIterator(listIter *iter) {
zfree(iter); // 直接調用zfree來釋放
}
重置疊代器
重置疊代器分為兩種,一種是重置正向疊代器,一種是重置為逆向疊代器
// 重置為正向疊代器
void listRewind(list *list, listIter *li) {
li->next = list->head;
li->direction = AL_START_HEAD;
}
// 重置為逆向疊代器
void listRewindTail(list *list, listIter *li) {
li->next = list->tail;
li->direction = AL_START_TAIL;
}
擷取下一個疊代器
// 根據direction屬性來擷取下一個疊代器
listNode *listNext(listIter *iter)
{
listNode *current = iter->next;
<span style="color:#66d9ef">if</span> (current <span style="color:#f92672">!=</span> NULL) {
<span style="color:#66d9ef">if</span> (iter<span style="color:#f92672">-></span>direction <span style="color:#f92672">==</span> AL_START_HEAD)
iter<span style="color:#f92672">-></span>next <span style="color:#f92672">=</span> current<span style="color:#f92672">-></span>next;
<span style="color:#66d9ef">else</span>
iter<span style="color:#f92672">-></span>next <span style="color:#f92672">=</span> current<span style="color:#f92672">-></span>prev;
}
<span style="color:#66d9ef">return</span> current;
}
連結清單複制函數
sdlist提供了listDup函數,用于複制整個連結清單。
list *listDup(list *orig)
{
list *copy;
listIter iter;
listNode *node;
<span style="color:#66d9ef">if</span> ((copy <span style="color:#f92672">=</span> listCreate()) <span style="color:#f92672">==</span> NULL)
<span style="color:#66d9ef">return</span> NULL;
<span style="color:#75715e">// 複制節點值操作函數
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match;
// 重置疊代器
listRewind(orig, &iter);
while((node = listNext(&iter)) != NULL) {
void *value;
// 複制節點
// 如果定義了dup函數,則按照dup函數來複制節點值
if (copy->dup) {
value = copy->dup(node->value);
if (value NULL) {
listRelease(copy);
return NULL;
}
} else // 如果沒有則直接指派
value = node->value;
// 依次向尾部添加節點
if (listAddNodeTail(copy, value) NULL) {
listRelease(copy);
return NULL;
}
}
return copy;
}
查找函數
sdlist提供了兩種查找函數。其一是根據給定節點值,在連結清單中查找該節點
listNode *listSearchKey(list *list, void *key)
{
listIter iter;
listNode *node;
listRewind(list, <span style="color:#f92672">&</span>iter);
<span style="color:#66d9ef">while</span>((node <span style="color:#f92672">=</span> listNext(<span style="color:#f92672">&</span>iter)) <span style="color:#f92672">!=</span> NULL) {
<span style="color:#66d9ef">if</span> (list<span style="color:#f92672">-></span>match) { <span style="color:#75715e">// 如果定義了match比對函數,則利用該函數進行節點比對
if (list->match(node->value, key)) {
return node;
}
} else { // 如果沒有定義match,則直接比較節點值
if (key == node->value) { // 找到該節點
return node;
}
}
}
// 沒有找到就傳回NULL
return NULL;
}
其二是根據序号來查找節點
listNode *listIndex(list *list, long index) {
listNode *n;
<span style="color:#66d9ef">if</span> (index <span style="color:#f92672"><</span> <span style="color:#ae81ff">0</span>) { <span style="color:#75715e">// 序号為負,則倒序查找
index = (-index)-1;
n = list->tail;
while(index– && n) n = n->prev;
} else { // 正序查找
n = list->head;
while(index– && n) n = n->next;
}
return n;
}
連結清單旋轉函數
旋轉操作其實就是講表尾節點移除,然後插入到表頭,成為新的表頭
void listRotate(list *list) {
listNode *tail = list->tail;
<span style="color:#66d9ef">if</span> (listLength(list) <span style="color:#f92672"><=</span> <span style="color:#ae81ff">1</span>) <span style="color:#66d9ef">return</span>;
<span style="color:#75715e">// 取出表尾指針
list->tail = tail->prev;
list->tail->next = NULL;
// 将其移動到表頭并成為新的表頭指針
list->head->prev = tail;
tail->prev = NULL;
tail->next = list->head;
list->head = tail;
}
sdlist小結
分析完sdlist的源碼,着實是把雙向連結清單的基本操作都複習了一遍,Redis的作者還真是喜歡造輪子,不愧是輪子界的鼻祖啊!雖然這些基本操作很簡單,但是可以學到一些優秀的設計,例如:sdlist疊代器的設計等,這些都對了解Redis的相關操作有着很大的幫助作用。
- Redis
- SourceCode
About Zeech Nothing to say « Previous
Redis源碼剖析--動态字元串sds
Next »
Redis源碼剖析--字典dict
Please enable JavaScript to view the comments powered by Disqus.
</div>