鏈表提供了高效的節點重排能力,以及順序性的節點訪問方式,而且能夠經過增刪節點來靈活地調整鏈表的長度。
鏈表是一種很是常見的數據結構。因爲 redis 使用的 C 語言並無這種數據結構,所以,做者在 redis 對這一數據結構進行了實現。redis 的鏈表實現爲雙向鏈表,主要用在實現列表鍵、發佈訂閱、保存多客戶端狀態、服務器模塊,訂閱模塊和保存輸入命令等方面,使用較廣。node
redis 源碼中關於 adlist 的部分,主要在 adlist.h
和 adlist.c
這兩個文件中。面試
首先在 adlist.h
中找到定義redis
// list 節點 typedef struct listNode { // 前驅節點 struct listNode *prev; // 後繼節點 struct listNode *next; // 節點值 void *value; } listNode; // redis 雙鏈表實現 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;
能夠發現,這就是一個無環雙向鏈表。list
結構中帶有一個 len
的變量,能夠將獲取鏈表長度的時間複雜度從 O(n) 降到 O(1)。head
指針和 tail
指針讓給咱們能夠快速的找到鏈表的頭尾,時間複雜度都是 O(1)。
三個函數指針,讓咱們能夠對鏈表有更靈活的操做,使用起來也更加方便。服務器
當須要進行鏈表迭代時,可使用以下函數:數據結構
typedef struct listIter { listNode *next; // 指向下一個節點 int direction; // 迭代器,正向反向 } listIter;
direction
決定了遍歷的方向,可正向可反向。函數
這部分定義了一些獲取 list
結構的宏,簡化操做。學習
#define listLength(l) ((l)->len) // 獲取 list 中包含的 node 數量 #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)
這部分定義了一些雙向鏈表的經常使用操做。設計
list *listCreate(void); // 建立一個不包含任何節點的新鏈表 void listRelease(list *list); // 釋放給定鏈表,以及鏈表中的全部節點 // CRUD 操做 list *listAddNodeHead(list *list, void *value); // 頭部插入節點 list *listAddNodeTail(list *list, void *value); // 尾部插入節點 list *listInsertNode(list *list, listNode *old_node, void *value, int after); // 中間某個位置插入節點 void listDelNode(list *list, listNode *node); // O(N) 刪除指定節點 listIter *listGetIterator(list *list, int direction); // 獲取指定迭代器 void listReleaseIterator(listIter *iter); // 釋放迭代器 listNode *listNext(listIter *iter); // 迭代下一個節點 list *listDup(list *orig); // 鏈表複製 listNode *listSearchKey(list *list, void *key); // O(N) 按 key 找節點 listNode *listIndex(list *list, long index); // O(N) void listRewind(list *list, listIter *li); // 重置爲正向迭代器 void listRewindTail(list *list, listIter *li); // 重置爲逆向迭代器 void listRotate(list *list); // 鏈表旋轉
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; }
建立一個空的 adlist 很簡單,就是分配內存,初始化數據結構,而 listRelease
的釋放鏈表過程與之相反,這個自沒必要多說。指針
首先是插入數據,分三種狀況:頭部插入、中間插入和尾部插入。
(1) 頭部插入code
// 頭部插入值 value list *listAddNodeHead(list *list, void *value) { listNode *node; if ((node = zmalloc(sizeof(*node))) == NULL) // 爲新節點分配內存 return NULL; node->value = value; if (list->len == 0) { // 若以前的 list 爲空,那麼插入後就只有一個節點 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 head 信息 } list->len++; // 更新鏈表長度信息 return list; }
(2)尾部插入節點相似,就不囉嗦了。
(3)中間插入
// 在 list 指定節點 old_node 後(after=1)或前插入一個節點 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 節點的先後指向 node->prev = old_node; node->next = old_node->next; if (list->tail == old_node) { // node 成了尾節點,更新 list 信息 list->tail = node; } } else { // 前 node->next = old_node; node->prev = old_node->prev; if (list->head == old_node) { // node 成了頭節點,更新 list 信息 list->head = node; } } // 處理 node 相鄰兩個節點的指向 if (node->prev != NULL) { node->prev->next = node; } if (node->next != NULL) { node->next->prev = node; } list->len++; return list; }
而後是刪除操做。
// 從 list 中刪除 node 節點 void listDelNode(list *list, listNode *node) { if (node->prev) // 是否有前驅節點,即判斷要刪除的節點是否爲頭節點 node->prev->next = node->next; else list->head = node->next; // 更新 list 的頭結點指向 if (node->next) // 是否有後繼節點,即判斷要刪除的節點是否爲尾節點 node->next->prev = node->prev; else list->tail = node->prev; if (list->free) list->free(node->value); zfree(node); list->len--; // 更新節點數量信息 }
最後是查找。
// 從 list 中查找 key listNode *listSearchKey(list *list, void *key) { listIter iter; listNode *node; listRewind(list, &iter); // 得到正向遍歷器,並從頭開始遍歷 while((node = listNext(&iter)) != NULL) { if (list->match) { // list 中有指定的比較器 if (list->match(node->value, key)) { return node; } } else { if (key == node->value) { return node; } } } return NULL; }
// 得到 list 中第 index 個節點,index 爲負數表示從尾部倒序往前找 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; }
迭代器實現以下:
listIter *listGetIterator(list *list, int direction) { listIter *iter; if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; if (direction == AL_START_HEAD) iter->next = list->head; else iter->next = list->tail; iter->direction = direction; // 迭代器方向 return iter; }
另外,一個旋轉 list 的操做,實現效果將 1 → 2 → 3 → 4 變成 4 → 1 → 2 → 3
void listRotate(list *list) { listNode *tail = list->tail;// 取尾節點 if (listLength(list) <= 1) return; // 1 個節點不須要 rotate /* Detach current tail 分離尾部節點*/ list->tail = tail->prev; list->tail->next = NULL; /* Move it as head 轉移到 head */ list->head->prev = tail; tail->prev = NULL; tail->next = list->head; list->head = tail; // 更新 list 的新 head }
adlist 其實就是把雙向鏈表的基本操做實現了一遍,看了一遍至關於複習了一遍(以前面試總問這些,哈哈),不過做者設計的很巧,值得學習。