Redis基本數據結構之雙向鏈表

鏈表提供了高效的節點重排能力,以及順序性的節點訪問方式,而且能夠經過增刪節點來靈活地調整鏈表的長度。

鏈表是一種很是常見的數據結構。因爲 redis 使用的 C 語言並無這種數據結構,所以,做者在 redis 對這一數據結構進行了實現。redis 的鏈表實現爲雙向鏈表,主要用在實現列表鍵、發佈訂閱、保存多客戶端狀態、服務器模塊,訂閱模塊和保存輸入命令等方面,使用較廣。node

redis 源碼中關於 adlist 的部分,主要在 adlist.hadlist.c 這兩個文件中。面試

adlist 的定義

首先在 adlist.h 中找到定義redis

// list 節點
typedef struct listNode {
    // 前驅節點
    struct listNode *prev;
    // 後繼節點
    struct listNode *next;
    // 節點值
    void *value;
} listNode;

// 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;

能夠發現,這就是一個無環雙向鏈表。
list 結構中帶有一個 len 的變量,能夠將獲取鏈表長度的時間複雜度從 O(n) 降到 O(1)。
head 指針和 tail 指針讓給咱們能夠快速的找到鏈表的頭尾,時間複雜度都是 O(1)。
三個函數指針,讓咱們能夠對鏈表有更靈活的操做,使用起來也更加方便。服務器

當須要進行鏈表迭代時,可使用以下函數:數據結構

typedef struct listIter {
    listNode *next; // 指向下一個節點
    int direction;  // 迭代器,正向反向
} listIter;

direction 決定了遍歷的方向,可正向可反向。函數

adlist 宏定義

這部分定義了一些獲取 list 結構的宏,簡化操做。學習

#define listLength(l) ((l)->len)                    // 獲取 list 中包含的 node 數量
#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)

adlist 函數

這部分定義了一些雙向鏈表的經常使用操做。設計

list *listCreate(void); // 建立一個不包含任何節點的新鏈表
void listRelease(list *list); // 釋放給定鏈表,以及鏈表中的全部節點

// CRUD 操做
list *listAddNodeHead(list *list, void *value);  // 頭部插入節點
list *listAddNodeTail(list *list, void *value);  // 尾部插入節點
list *listInsertNode(list *list, listNode *old_node, void *value, int after); // 中間某個位置插入節點
void listDelNode(list *list, listNode *node); // O(N) 刪除指定節點

listIter *listGetIterator(list *list, int direction); // 獲取指定迭代器
void listReleaseIterator(listIter *iter);   // 釋放迭代器
listNode *listNext(listIter *iter); // 迭代下一個節點

list *listDup(list *orig); // 鏈表複製
listNode *listSearchKey(list *list, void *key); // O(N) 按 key 找節點
listNode *listIndex(list *list, long index);  // O(N)
void listRewind(list *list, listIter *li); // 重置爲正向迭代器
void listRewindTail(list *list, listIter *li); // 重置爲逆向迭代器
void listRotate(list *list); // 鏈表旋轉

建立 adlist

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;
}

建立一個空的 adlist 很簡單,就是分配內存,初始化數據結構,而 listRelease 的釋放鏈表過程與之相反,這個自沒必要多說。指針

adlist 的 CRUD 操做

首先是插入數據,分三種狀況:頭部插入、中間插入和尾部插入。
(1) 頭部插入code

// 頭部插入值 value
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL) // 爲新節點分配內存
        return NULL;
    node->value = value;
    if (list->len == 0) { // 若以前的 list 爲空,那麼插入後就只有一個節點
        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 head 信息
    }
    list->len++; // 更新鏈表長度信息
    return list;
}

(2)尾部插入節點相似,就不囉嗦了。
(3)中間插入

// 在 list 指定節點 old_node 後(after=1)或前插入一個節點
list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL) // 爲新節點分配內存
        return NULL;
    node->value = value;
    if (after) { // 後
    
        // 處理 node 節點的先後指向
        node->prev = old_node;
        node->next = old_node->next;
        if (list->tail == old_node) { // node 成了尾節點,更新 list 信息
            list->tail = node;
        }
    } else { // 前
        node->next = old_node;
        node->prev = old_node->prev;
        if (list->head == old_node) { // node 成了頭節點,更新 list 信息
            list->head = node;
        }
    }
    
    // 處理 node 相鄰兩個節點的指向
    if (node->prev != NULL) { 
        node->prev->next = node;
    }
    if (node->next != NULL) {
        node->next->prev = node;
    }
    list->len++;
    return list;
}

而後是刪除操做。

// 從 list 中刪除 node 節點
void listDelNode(list *list, listNode *node)
{
    if (node->prev) // 是否有前驅節點,即判斷要刪除的節點是否爲頭節點
        node->prev->next = node->next;
    else
        list->head = node->next; // 更新 list 的頭結點指向
    if (node->next) // 是否有後繼節點,即判斷要刪除的節點是否爲尾節點
        node->next->prev = node->prev;
    else
        list->tail = node->prev;
    if (list->free) list->free(node->value);
    zfree(node);
    list->len--; // 更新節點數量信息
}

最後是查找。

// 從 list 中查找 key
listNode *listSearchKey(list *list, void *key)
{
    listIter iter;
    listNode *node;

    listRewind(list, &iter); // 得到正向遍歷器,並從頭開始遍歷
    while((node = listNext(&iter)) != NULL) {
        if (list->match) { // list 中有指定的比較器
            if (list->match(node->value, key)) {
                return node;
            }
        } else {
            if (key == node->value) {
                return node;
            }
        }
    }
    return NULL;
}
// 得到 list 中第 index 個節點,index 爲負數表示從尾部倒序往前找
listNode *listIndex(list *list, long index) {
    listNode *n;
    if (index < 0) { // 從尾部查找
        index = (-index)-1;
        n = list->tail;
        while(index-- && n) n = n->prev; // 往前遍歷
    } else {
        n = list->head;
        while(index-- && n) n = n->next; // 日後遍歷
    }
    return n;
}

其餘

迭代器實現以下:

listIter *listGetIterator(list *list, int direction)
{
    listIter *iter;

    if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
    if (direction == AL_START_HEAD)
        iter->next = list->head;
    else
        iter->next = list->tail;
    iter->direction = direction; // 迭代器方向
    return iter;
}

另外,一個旋轉 list 的操做,實現效果將 1 → 2 → 3 → 4 變成 4 → 1 → 2 → 3

void listRotate(list *list) {
    listNode *tail = list->tail;// 取尾節點

    if (listLength(list) <= 1) return; // 1 個節點不須要 rotate

    /* Detach current tail 分離尾部節點*/
    list->tail = tail->prev;
    list->tail->next = NULL;

    /* Move it as head 轉移到 head */
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;

    list->head = tail; // 更新 list 的新 head
}

總結

adlist 其實就是把雙向鏈表的基本操做實現了一遍,看了一遍至關於複習了一遍(以前面試總問這些,哈哈),不過做者設計的很巧,值得學習。

相關文章
相關標籤/搜索