Redis 2.8.9源碼 - Redis中的雙端鏈表實現 adlist

本文爲做者原創,轉載請註明出處:http://my.oschina.net/fuckphp/blog/269801php

adlist做爲Redis中的雙端鏈表,在Redis中被普遍的應用到了不少地方,好比 slowlog的存儲,主從複製中報錯客戶端,list數據結構的實現等,不少都與此相關,因此也是很是重要的一個數據結構。node

Redis 對雙端鏈表的描述和實現源碼在 src/adlist.h   src/adlist.c,關於學習Redis雙端鏈表如何進行測試或debug,請參考另一篇文章:Redis 2.8.9源碼 - 雙向鏈表操做函數頭整理,並註釋做用和參數說明(附測試方法和代碼)redis


一)、Redis中雙端鏈表的數據結構數據結構

雙端鏈表(如下代碼定義在 src/adlist.h):函數

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;

雙端鏈表的節點(如下代碼定義在 src/adlist.h)學習

typedef struct listNode {
    struct listNode *prev;   //當前節點的上一個節點
    struct listNode *next;   //當前節點的下一個節點
    void *value;     //當前節點存儲的值,能夠任意類型
} listNode;

引用一張來自《Redis 設計與實現 初版》 的一張圖測試

list 經過 head 和 tail兩個指針分別指向鏈表的頭尾兩端,使得遍歷鏈表能夠從正反兩個順序進行遍歷,而 listNode 的void *value,則能夠保存任意數據,並能夠經過dup,free,match來自定義如何處理listNode的值。spa

相關例子請看另外一篇文章: 雙向鏈表操做函數頭整理,並註釋做用和參數說明(附測試方法和代碼) 中的測試代碼中的實例..net

2、雙端鏈表的簡單操做
debug

建立鏈表(如下代碼定義在 src/adlist.c)

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

追加到鏈表結尾(如下代碼定義在 src/adlist.c)

list *listAddNodeTail(list *list, void *value)
{
    listNode *node;    //初始化node節點

    //爲新的node節點分配內存
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    //爲node節點設置值
    node->value = value;
    
    if (list->len == 0) {
        //若是空鏈表則 將頭尾指向 新節點 並後繼前驅節點設置爲NULL
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        //不然將節點的前驅節點設置爲原來的尾部節點
        node->prev = list->tail;
        //因爲追加到尾部,後繼節點爲NULL
        node->next = NULL;
        //以前的尾部節點的後繼節點設置爲新增的節點
        list->tail->next = node;
        //將列表的尾部節點指針指向新增的節點
        list->tail = node;
    }
    //增長鏈表長度
    list->len++;
    return list;
}

遍歷鏈表:

經常使用的遍歷鏈表有兩種方式,一個是根據鏈表長度經過while循環去手動遍歷,還有一種是用Redis雙端鏈表提供的迭代器來遍歷。

手動遍歷(如下代碼定義在 src/adlist.c 中的 內存釋放函數)

void listRelease(list *list)
{
    unsigned long len;
    //current表示當前遍歷的遊標指針,next表示下次遍歷的遊標指針(next做爲臨時變量用於保存下一個節點)
    listNode *current, *next;
    //將current指向頭部節點
    current = list->head;
    //計算長度(其實就是 listLength(list))
    len = list->len;
    //經過長度遞減的方式進行遍歷,便利到長度爲0的時候,遍歷結束
    while(len--) {
        //保存下次循環的節點指針
        next = current->next;
        //釋放掉當前節點,若是設置釋放節點的回調函數,則執行用戶自定義的函數
        if (list->free) list->free(current->value);
        zfree(current);
        //將遊標指向下個節點
        current = next;
    }
    zfree(list);
}

迭代器方式遍歷請見下文。

三)、雙端鏈表的迭代器

Redis 爲了方便對鏈表的迭代,對鏈表進行了迭代器的封裝,迭代器結構以下(如下代碼定義在 src/adlist.h):

typedef struct listIter {
    listNode *next;    //迭代器指向的下一個節點
    //迭代方向,因爲雙端鏈表保存了head和tail的值,因此能夠進行兩個方向的迭代
    //AL_START_HEAD表示從頭向後遍歷,AL_START_TAIL表示從尾部向前遍歷
    int direction;
} listIter;

 迭代器使用實例:

//l表示list結構,n表示listNode結構,listNode的值保存的是sds字符串
//建立迭代器對象
iter = listGetIterator(l, AL_START_HEAD);
//循環獲取下一個須要遍歷的節點
while ((n = listNext(iter))) {
    //輸出返回的節點n的value值
    printf("Node Value: %s\n", listNodeValue(n));
}


更多實例和Redis爲鏈表提供的Api,請參考另一篇文章:

Redis 2.8.9源碼 - 雙向鏈表操做函數頭整理,並註釋做用和參數說明(附測試方法和代碼)




參考資料:

Redis2.8.9源碼   src/adlist.h   src/adlist.c

Redis 設計與實現(初版)

相關文章
相關標籤/搜索