Redis源碼剖析(八)鏈表

來源:互聯網
上載者:User

在之前對Redis的介紹中,可以看到鏈表的使用頻率非常高。

鏈表可以作為單獨的儲存結構,比如用戶端的監視鏈表記錄該用戶端監視的所有鍵,伺服器的模式訂閱鏈表記錄所有用戶端和它的模式訂閱。

鏈表也可以內嵌到字典中作為字典的實值型別,比如資料庫的監視字典使用鏈表格儲存體監視某個鍵的所有用戶端,伺服器的訂閱字典使用鏈表格儲存體訂閱某個頻道的所有用戶端。 鏈表結構 節點

Redis中的鏈表是雙向鏈表,即每一個節點都儲存了它的前驅節點和後繼節點,用於提高操作效率。節點定義如下

//adlist.h/* 鏈表節點 */typedef struct listNode {    struct listNode *prev; /* 前驅節點 */    struct listNode *next; /* 後繼節點 */    void *value;    /* 值 */} listNode;
鏈表

鏈表結構主要記錄了表前端節點和表尾節點,節點個數以及一些函數指標,定義如下

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

函數指標主要是對節點值的操作,包括複製,析構,判斷是否相等 迭代器

此外,Redis還為鏈表提供迭代器的功能,主要是對鏈表節點的封裝,另外通過鏈表節點的前驅節點和後繼節點,可以輕鬆的完成向前移動和向後移動

//adlist.h/* 迭代器 */typedef struct listIter {    /* 指向實際的節點 */    listNode *next;    /* 迭代器方向,向前還是向後 */    int direction;} listIter;

direction的值有兩個,向前和向後,由宏定義指出

//adlist.h#define AL_START_HEAD 0 /* 從頭到尾(向後) */#define AL_START_TAIL 1 /* 從尾到頭(向前) */
鏈表操作 建立鏈表

鏈表的建立工作由listCreate函數完成,實際上就是申請鏈表記憶體然後初始化成員變數

//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;}
刪除鏈表

刪除一個鏈表比建立稍微麻煩一點,因為需要釋放每個節點中儲存的值,沒錯,它正是調用free函數完成的

//adlist.c/* 釋放鏈表的記憶體空間 */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;    }    /* 因為list* 也是動態申請的,所以也需要釋放 */    zfree(list);}
在末尾插入節點

在其他模組的實現上,經常會看到向鏈表尾部添加節點的操作,它的實現由listAddNodeTail完成。函數首先為新節點申請記憶體,然後將節點添加到鏈表中,這裡需要根據鏈表之前是否為空白執行不同操作 鏈表為空白,新節點將作為鏈表的前端節點和尾節點,新節點的前驅和後繼指標都為空白 鏈表非空,新節點將作為鏈表的尾節點,之前的尾節點的後繼指標指向新節點,新節點的前驅指標指向之前的尾節點

//adlist.c/* 在鏈表尾部添加節點 */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++;    return list;}
迭代器移動

迭代器主要用於遍曆鏈表,而迭代器的重點在移動上,通過direction變數,可以得知迭代器移動的方向,又通過鏈表節點的前驅後繼節點,可以輕鬆實現移動操作

//adlist.c/* 移動迭代器,同時返回下一個節點 */listNode *listNext(listIter *iter){    /* next指標是當前迭代器指向的節點指標 */    listNode *current = iter->next;    if (current != NULL) {        /* 根據方向為next賦值 */        if (iter->direction == AL_START_HEAD)            iter->next = current->next;        else            iter->next = current->prev;    }    /* 返回之前迭代器指向的節點 */    return current;}
重設迭代器

此外,Redis提供了重設迭代器的操作,分別由listRewind和listRewindTail函數完成

/* 重設迭代器方向為從頭到尾,使迭代器指向前端節點 */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;}
鏈表搜尋

有了迭代器的基礎,就可以實現鏈表搜尋功能,即在鏈表中尋找與某個值匹配的節點,需要利用迭代器遍曆鏈表

//adlist.c/* 尋找值key,返回鏈表節點 */listNode *listSearchKey(list *list, void *key){    listIter iter;    listNode *node;    /* 設定迭代器方向為從頭到尾,使其指向鏈表前端節點 */    listRewind(list, &iter);    /* 遍曆鏈表 */    while((node = listNext(&iter)) != NULL) {        /* 如果提供值匹配函數,則調用,否則使用==比較 */        if (list->match) {            if (list->match(node->value, key)) {                return node;            }        } else {            if (key == node->value) {                return node;            }        }    }    return NULL;}
宏定義函數

除了上面提到的函數外,Redis還提供了一些宏定義函數,比如返回節點值,返回節點的前驅後繼節點等

//adlist.h/* 返回鏈表節點個數 */#define listLength(l) ((l)->len)/* 返回前端節點 */#define listFirst(l) ((l)->head)/* 返回尾節點 */#define listLast(l) ((l)->tail)/* 返回前驅節點 */#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)
小結

由於鏈表結構簡單,所以在實現上還是非常容易理解的。當然Redis中與鏈表有關的函數還有很多很多,這裡僅僅介紹了一些常用操作,有興趣可以深入源碼查看

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.