在之前對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中與鏈表有關的函數還有很多很多,這裡僅僅介紹了一些常用操作,有興趣可以深入源碼查看