1. 程式人生 > >Redis原始碼剖析--雙端連結串列Sdlist

Redis原始碼剖析--雙端連結串列Sdlist

> 請持續關注我的個人部落格:https://zcheng.ren 今天來分析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在實現連結串列的時候,定義其為雙端無環連結串列,其示意圖如下:

此外,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 = list->head;
    len = list->len;
    while(len--) {
        next = current->next;
        // 如果定義了節點值釋放函式,需要呼叫
        if (list->free) list->free(current->value);
        zfree(current);  // 釋放當前節點
        current = next;
    }
    zfree(list);  // 釋放連結串列頭
}

插入節點

sdlist提供了三個函式來完成向list中插入一個節點的功能。

向頭部插入節點

// 該函式向list的頭部插入一個節點
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) { // 如果連結串列為空
        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;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (list->len == 0) { // 如果連結串列為空
        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;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (after) { // 向後插入
        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;

    if (current != NULL) {
        if (iter->direction == AL_START_HEAD)
            iter->next = current->next;
        else
            iter->next = current->prev;
    }
    return current;
}

連結串列複製函式

sdlist提供了listDup函式,用於複製整個連結串列。

list *listDup(list *orig)
{
    list *copy;
    listIter iter;
    listNode *node;

    if ((copy = listCreate()) == NULL)
        return NULL;
    // 複製節點值操作函式
    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, &iter);
    while((node = listNext(&iter)) != NULL) {
        if (list->match) { // 如果定義了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;

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

連結串列旋轉函式

旋轉操作其實就是講表尾節點移除,然後插入到表頭,成為新的表頭

void listRotate(list *list) {
    listNode *tail = list->tail;

    if (listLength(list) <= 1) return;

    // 取出表尾指標
    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的相關操作有著很大的幫助作用。