Redis源码剖析(八)链表
在之前對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完成。函數首先為新節點申請內存,然后將節點添加到鏈表中,這里需要根據鏈表之前是否為空執行不同操作
- 鏈表為空,新節點將作為鏈表的頭節點和尾節點,新節點的前驅和后繼指針都為空
- 鏈表非空,新節點將作為鏈表的尾節點,之前的尾節點的后繼指針指向新節點,新節點的前驅指針指向之前的尾節點
迭代器移動
迭代器主要用于遍歷鏈表,而迭代器的重點在移動上,通過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;elseiter->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中與鏈表有關的函數還有很多很多,這里僅僅介紹了一些常用操作,有興趣可以深入源碼查看
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Redis源码剖析(八)链表的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Redis源码剖析(七)监视功能
- 下一篇: Redis源码剖析(九)对象系统概述