本文爲做者原創,轉載請註明出處: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