redis中雙向鏈表相關的文件爲:adlist.h與adlist.cgit
1、數據結構github
redis裏定義的雙向鏈表,與普通雙向鏈表大體相同redis
單個節點:數據結構
1 typedef struct listNode { 2 struct listNode *prev; 3 struct listNode *next; 4 void *value; 5 } listNode;
鏈表:函數
1 typedef struct list { 2 listNode *head; 3 listNode *tail; 4 void *(*dup)(void *ptr); 5 void (*free)(void *ptr); 6 int (*match)(void *ptr, void *key); 7 unsigned long len; 8 } list;
鏈表以函數指針的方式,實現了複製、銷燬與比較的方法的多態。spa
迭代器:指針
1 typedef struct listIter { 2 listNode *next; 3 int direction; 4 } listIter;
迭代器中有個成員變量direction,用於表示當前遍歷的方向。code
大體結構:blog
1 /* 2 +-------------------+ +----------------> +--------------+ <-------+ 3 |listNode *head |--------+ |listNode *prev|-->NULL | 4 +-------------------+ +--------------+ | 5 |listNode *tail |--------+ |listNode *next|----+ | 6 +-------------------+ | +--------------+ | | 7 |void *(*dup)(...) | | |void *value | | | 8 +-------------------+ | +--------------+ | | 9 |void (*free)(...) | | | | 10 +-------------------+ | | | 11 |int (*match)(...) | | | | 12 +-------------------+ +----------------> +--------------+ <--+ | 13 |unsigned long len | |listNode *prev|---------+ 14 +-------------------+ +--------------+ 15 |listNode *next|-->NULL 16 +--------------+ 17 |void *value | 18 +--------------+ 19 */
2、建立接口
redis中建立一個初始雙向鏈表比較簡單,只要分配好內存,並給成員變量賦初值就能夠了
1 list *listCreate(void) 2 { 3 struct list *list; 4 5 if ((list = zmalloc(sizeof(*list))) == NULL) 6 return NULL; 7 list->head = list->tail = NULL; 8 list->len = 0; 9 list->dup = NULL; 10 list->free = NULL; 11 list->match = NULL; 12 return list; 13 }
redis中提供了頭插法、尾插法以及指定位置插入節點三種方式向鏈表中添加節點,與普通雙向鏈表無異,此處不作詳細敘述。
3、銷燬
因鏈表中每一個節點的value可能指向堆空間,故不能直接把list結構體free,這樣會形成內存泄露。須要先將每一個節點的value釋放,才能夠free結構體
清空全部節點:
1 void listEmpty(list *list) 2 { 3 unsigned long len; 4 listNode *current, *next; 5 6 current = list->head; 7 len = list->len; 8 while(len--) { 9 next = current->next; 10 //若指定了銷燬的函數,則使用指定的函數進行銷燬value 11 if (list->free) list->free(current->value); 12 zfree(current); 13 current = next; 14 } 15 list->head = list->tail = NULL; 16 list->len = 0; 17 }
銷燬鏈表:
1 void listRelease(list *list) 2 { 3 listEmpty(list); 4 zfree(list); 5 }
一樣,redis的鏈表提供了與普通鏈表相同的刪除單個節點的操做,此處也不作敘述。
4、迭代器操做
redis中提供了獲取迭代器的接口
1 listIter *listGetIterator(list *list, int direction) 2 { 3 listIter *iter; 4 5 if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL; 6 if (direction == AL_START_HEAD) 7 iter->next = list->head; 8 else 9 iter->next = list->tail; 10 iter->direction = direction; 11 return iter; 12 }
以AL_START_HEAD爲例,生成好的迭代器結構以下:
1 /* 2 +-------------------+ +---> +--------------+ <-------+----+ 3 |listNode *head |----+ |listNode *prev|-->NULL | | 4 +-------------------+ +--------------+ | | +--------------+ 5 |listNode *tail |----+ |listNode *next|----+ | +--|listNode *next| 6 +-------------------+ | +--------------+ | | +--------------+ 7 |void *(*dup)(...) | | |void *value | | | |int direction | 8 +-------------------+ | +--------------+ | | +--------------+ 9 |void (*free)(...) | | | | 10 +-------------------+ | | | 11 |int (*match)(...) | | | | 12 +-------------------+ +---> +--------------+ <--+ | 13 |unsigned long len | |listNode *prev|---------+ 14 +-------------------+ +--------------+ 15 |listNode *next|-->NULL 16 +--------------+ 17 |void *value | 18 +--------------+ 19 */
迭代器的next方法:
1 listNode *listNext(listIter *iter) 2 { 3 listNode *current = iter->next; 4 5 if (current != NULL) { 6 if (iter->direction == AL_START_HEAD) 7 iter->next = current->next; 8 else 9 iter->next = current->prev; 10 } 11 return current; 12 }
調用一次以後的結構:
1 /* 2 +-------------------+ +---> +--------------+ <-------+ 3 |listNode *head |----+ |listNode *prev|-->NULL | 4 +-------------------+ +--------------+ | +--------------+ 5 |listNode *tail |----+ |listNode *next|----+ | +--|listNode *next| 6 +-------------------+ | +--------------+ | | | +--------------+ 7 |void *(*dup)(...) | | |void *value | | | | |int direction | 8 +-------------------+ | +--------------+ | | | +--------------+ 9 |void (*free)(...) | | | | | 10 +-------------------+ | | | | 11 |int (*match)(...) | | | | | 12 +-------------------+ +---> +--------------+ <--+----|----+ 13 |unsigned long len | |listNode *prev|---------+ 14 +-------------------+ +--------------+ 15 |listNode *next|-->NULL 16 +--------------+ 17 |void *value | 18 +--------------+ 19 */
再次調用:
1 /* 2 +-------------------+ +---> +--------------+ <-------+ 3 |listNode *head |----+ |listNode *prev|-->NULL | 4 +-------------------+ +--------------+ | +--------------+ 5 |listNode *tail |----+ |listNode *next|----+ | +--|listNode *next| 6 +-------------------+ | +--------------+ | | | +--------------+ 7 |void *(*dup)(...) | | |void *value | | | | |int direction | 8 +-------------------+ | +--------------+ | | | +--------------+ 9 |void (*free)(...) | | | | | 10 +-------------------+ | | | | 11 |int (*match)(...) | | | | | 12 +-------------------+ +---> +--------------+ <--+ | +-->NULL 13 |unsigned long len | |listNode *prev|---------+ 14 +-------------------+ +--------------+ 15 |listNode *next|-->NULL 16 +--------------+ 17 |void *value | 18 +--------------+ 19 */
調用next函數的返回值爲調用以前的listNode首地址
5、其它操做
redis的雙向鏈表還提供了其它操做。其中,查找指定的key與複製整個list依賴於迭代器的使用,並使用到自定義的比較/複製方法。
除此以外,還提供了相似隨機讀取的方式,其內部實現爲遍歷,且「越界」時返回NULL。同時,它支持index爲負數,表示從尾開始。相似旋轉的操做,把尾節點移至原頭節點以前,成爲新的頭節點。固然,還有拼接兩個鏈表的操做。
redis 5.0.7 下載連接
http://download.redis.io/releases/redis-5.0.7.tar.gz
源碼閱讀順序參考:
https://github.com/huangz1990/blog/blob/master/diary/2014/how-to-read-redis-source-code.rst