Redis定義了雙向連結串列,用來儲存列表鍵的值,還有其他的我不知的。
雙向連結串列的節點定義為:
typedef struct listNode {
// 前置節點
struct listNode *prev;
// 後置節點
struct listNode *next;
// 節點的值
void *value;
} listNode;
這裡是把節點的值作為了一個成員變數,Linux核心裡面節點的定義是這樣的
struct list_head{
struct list_head *next;
struct list_head *prev;
}
這樣可以沒有不用處理節點所帶資料的型別,但是要通過offsetof/container_of這類來處理。不過這是題外話了。
除此之外,Redis還定義了連結串列結構,如下:
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;
這樣定義的好處有:
-
快速獲取連結串列的頭尾
-
快速查詢連結串列的節點數,像動態字串sdshr裡的len那樣,不用遍歷全部字元就可以獲取字串的長度,這裡也類似。
它裡面還定義了連結串列的迭代器,
typedef struct listIter {
// 當前迭代到的節點
listNode *next;
// 迭代的方向
int direction;
} listIter;
裡面迭代的方向為
// 從表頭向表尾進行迭代
#define AL_START_HEAD 0
// 從表尾到表頭進行迭代
#define AL_START_TAIL 1
迭代器可以通過以下幾個介面來使用(不完全列出來)
listIter *listGetIterator(list *list, int direction) //根據迭代方向給連結串列建立迭代器
listNode *listNext(listIter *iter) //返回迭代器當前指向的節點,並且把迭代器根據方向向前移動一下
假如我要遍歷整個連結串列,我可以這樣做
iter = listGetIterator(list, AL_START_HEAD);
while ((node = listNext(iter)) != NULL) {
doSomethingWith(node);
}
寫起來是比較簡單易懂的。
裡面還有個反轉節點的函式
void listRotate(list *list) {
listNode *tail = list->tail;
if (listLength(list) <= 1) return;
/* Detach current tail */
// 取出表尾節點
list->tail = tail->prev;
list->tail->next = NULL;
/* Move it as head */
// 插入到表頭
list->head->prev = tail;
tail->prev = NULL;
tail->next = list->head;
list->head = tail;
}
這個其實是翻轉節點,不是倒序。是不是跟以前做過的演算法題很像。有沒有想起手搖演算法。
最後貼上《Redis設計與實現》裡的總結
連結串列被廣泛用於實現 Redis 的各種功能, 比如列表鍵, 釋出與訂閱, 慢查詢, 監視器, 等等。
每個連結串列節點由一個 listNode 結構來表示, 每個節點都有一個指向前置節點和後置節點的指標, 所以 Redis 的連結串列實現是雙端連結串列。
每個連結串列使用一個 list 結構來表示, 這個結構帶有表頭節點指標、表尾節點指標、以及連結串列長度等資訊。
因為連結串列表頭節點的前置節點和表尾節點的後置節點都指向 NULL , 所以 Redis 的連結串列實現是無環連結串列。
通過為連結串列設定不同的型別特定函式, Redis 的連結串列可以用於儲存各種不同型別的值。
參考:
1.《Redis設計與實現》
2.https://github.com/huangz1990…