1. 程式人生 > 其它 >redis 5.0.2 原始碼閱讀——雙向連結串列

redis 5.0.2 原始碼閱讀——雙向連結串列

redis中雙向連結串列相關的檔案為:adlist.h與adlist.c

一、資料結構

  redis裡定義的雙向連結串列,與普通雙向連結串列大致相同

單個節點:

1 /* Node, List, and Iterator are the only data structures used currently. 連結串列節點*/
2 
3 typedef struct listNode {
4     struct listNode *prev;
5     struct listNode *next;
6     void *value;
7 } listNode;

連結串列:

 1 /**
2 * 連結串列 3 * 連結串列以函式指標的方式,實現了複製、銷燬與比較的方法的多型。 4 */ 5 typedef struct list { 6 listNode *head; 7 listNode *tail; 8 void *(*dup)(void *ptr); 9 void (*free)(void *ptr); //指定的節點釋放函式 10 int (*match)(void *ptr, void *key); 11 unsigned long len; 12 } list;

迭代器:

1 /**
2  * 迭代器
3 * 迭代器中有個成員變數direction,用於表示當前遍歷的方向。 4 */ 5 typedef struct listIter { 6 listNode *next; 7 int direction; 8 } listIter;

大致結構:

 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 */

二、建立

  redis中建立一個初始雙向連結串列比較簡單,只要分配好記憶體,並給成員變數賦初值就可以了

 1 /* Create a new list. The created list can be freed with
 2  * AlFreeList(), but private value of every node need to be freed
 3  * by the user before to call AlFreeList().
 4  *
 5  * On error, NULL is returned. Otherwise the pointer to the new list.
 6  * redis中建立一個初始雙向連結串列比較簡單,只要分配好記憶體,並給成員變數賦初值就可以了
 7  * redis中提供了頭插法、尾插法以及指定位置插入節點三種方式向連結串列中新增節點,與普通雙向連結串列無異。
 8  *  */
 9 list *listCreate(void)
10 {
11     struct list *list;
12 
13     if ((list = zmalloc(sizeof(*list))) == NULL)
14         return NULL;
15     list->head = list->tail = NULL;
16     list->len = 0;
17     list->dup = NULL;
18     list->free = NULL;
19     list->match = NULL;
20     return list;
21 }

  redis中提供了頭插法、尾插法以及指定位置插入節點三種方式向連結串列中新增節點,與普通雙向連結串列無異。

頭插法:

 1 /* Add a new node to the list, to head, containing the specified 'value'
 2  * pointer as value.
 3  *
 4  * On error, NULL is returned and no operation is performed (i.e. the
 5  * list remains unaltered).
 6  * On success the 'list' pointer you pass to the function is returned.
 7  * 頭插法
 8  * */
 9 list *listAddNodeHead(list *list, void *value)
10 {
11     listNode *node;
12 
13     if ((node = zmalloc(sizeof(*node))) == NULL)
14         return NULL;
15     node->value = value;
16     if (list->len == 0) {
17         list->head = list->tail = node;
18         node->prev = node->next = NULL;
19     } else {
20         node->prev = NULL;
21         node->next = list->head;
22         list->head->prev = node;
23         list->head = node;
24     }
25     list->len++;
26     return list;
27 }

尾插法:

 1 /* Add a new node to the list, to tail, containing the specified 'value'
 2  * pointer as value.
 3  *
 4  * On error, NULL is returned and no operation is performed (i.e. the
 5  * list remains unaltered).
 6  * On success the 'list' pointer you pass to the function is returned.
 7  * 尾插法
 8  *  */
 9 list *listAddNodeTail(list *list, void *value)
10 {
11     listNode *node;
12 
13     if ((node = zmalloc(sizeof(*node))) == NULL)
14         return NULL;
15     node->value = value;
16     if (list->len == 0) {
17         list->head = list->tail = node;
18         node->prev = node->next = NULL;
19     } else {
20         node->prev = list->tail;
21         node->next = NULL;
22         list->tail->next = node;
23         list->tail = node;
24     }
25     list->len++;
26     return list;
27 }
 1 list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
 2     listNode *node;
 3 
 4     if ((node = zmalloc(sizeof(*node))) == NULL)
 5         return NULL;
 6     node->value = value;
 7     //如果為真,插在指定節點的後面,否則插在指定節點的前面
 8     if (after) {
 9         node->prev = old_node;
10         node->next = old_node->next;
11         if (list->tail == old_node) {
12             list->tail = node;
13         }
14     } else {
15         node->next = old_node;
16         node->prev = old_node->prev;
17         if (list->head == old_node) {
18             list->head = node;
19         }
20     }
21     if (node->prev != NULL) {
22         node->prev->next = node;
23     }
24     if (node->next != NULL) {
25         node->next->prev = node;
26     }
27     list->len++;
28     return list;
29 }

三、銷燬

  因連結串列中每個節點的value可能指向堆空間,故不能直接把list結構體free,這樣會造成記憶體洩露。需要先將每個節點的value釋放,才可以free結構體

清空所有節點:

 1 /**
 2  * Remove all the elements from the list without destroying the list itself.
 3  *
 4  * 清空所有節點:
 5  *
 6  * 因連結串列中每個節點的value可能指向堆空間,故不能直接把list結構體free,這樣會造成記憶體洩露。
 7  * 需要先將每個節點的value釋放,才可以free結構體
 8 */
 9 void listEmpty(list *list)
10 {
11     unsigned long len;
12     listNode *current, *next;
13 
14     current = list->head;
15     len = list->len;
16     while(len--) {
17         next = current->next;
18         //若指定了銷燬的函式,則使用指定的函式進行銷燬value
19         if (list->free)
20             list->free(current->value);
21         zfree(current);
22         current = next;
23     }
24     list->head = list->tail = NULL;
25     list->len = 0;
26 }
27 
28 /* Free the whole list.
29  *
30  * This function can't fail.
31  *
32  * 銷燬連結串列
33  *  */
34 void listRelease(list *list)
35 {
36     listEmpty(list);
37     zfree(list);
38 }

  同樣,redis的連結串列提供了與普通連結串列相同的刪除單個節點的操作。

刪除指定節點:

 1 /* Remove the specified node from the specified list.
 2  * It's up to the caller to free the private value of the node.
 3  *
 4  * This function can't fail.
 5  *
 6  * 從連結串列中刪除指定節點
 7  *  */
 8 void listDelNode(list *list, listNode *node)
 9 {
10     if (node->prev)
11         node->prev->next = node->next;
12     else
13         list->head = node->next;
14     if (node->next)
15         node->next->prev = node->prev;
16     else
17         list->tail = node->prev;、
18 
19     //如果該節點指定了自己的釋放函式就使用自己的釋放函式
20     if (list->free)
21         list->free(node->value);
22     zfree(node);
23 
24     //修正連結串列長度
25     list->len--;
26 }

四、迭代器操作

  redis中提供了獲取迭代器的介面

得到連結串列的指定遍歷方向的迭代器:

 1 /* Returns a list iterator 'iter'. After the initialization every
 2  * call to listNext() will return the next element of the list.
 3  *
 4  * This function can't fail.
 5  *
 6  * 得到一個遍歷連結串列的迭代器
 7  * */
 8 listIter *listGetIterator(list *list, int direction)
 9 {
10     listIter *iter;
11 
12     if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
13     //從頭開始遍歷還是從尾開始遍歷
14     if (direction == AL_START_HEAD)
15         iter->next = list->head;
16     else
17         iter->next = list->tail;
18 
19     //指定遍歷方向
20     iter->direction = direction;
21     return iter;
22 }

以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 /* Return the next element of an iterator.
 2  * It's valid to remove the currently returned element using
 3  * listDelNode(), but not to remove other elements.
 4  *
 5  * The function returns a pointer to the next element of the list,
 6  * or NULL if there are no more elements, so the classical usage patter
 7  * is:
 8  *
 9  * iter = listGetIterator(list,<direction>);
10  * while ((node = listNext(iter)) != NULL) {
11  *     doSomethingWith(listNodeValue(node));
12  * }
13  *
14  * 迭代器的next方法
15  * 呼叫next函式的返回值為呼叫之前的listNode首地址
16  * */
17 listNode *listNext(listIter *iter)
18 {
19     listNode *current = iter->next;
20 
21     if (current != NULL) {
22         if (iter->direction == AL_START_HEAD)
23             iter->next = current->next;
24         else
25             iter->next = current->prev;
26     }
27     return current;
28 }

呼叫一次之後的結構:

 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 */
得到連結串列的從頭開始遍歷的迭代器:
1 /* Create an iterator in the list private iterator structure 返回從頭開始遍歷的迭代器*/
2 void listRewind(list *list, listIter *li) {
3     li->next = list->head;
4     li->direction = AL_START_HEAD;
5 }

得到連結串列的從尾開始遍歷的迭代器:

1 //返回從尾開始遍歷的迭代器
2 void listRewindTail(list *list, listIter *li) {
3     li->next = list->tail;
4     li->direction = AL_START_TAIL;
5 }

釋放迭代器:

1 /* Release the iterator memory 釋放迭代器*/
2 void listReleaseIterator(listIter *iter) {
3     zfree(iter);
4 }

五、其它操作

  redis的雙向連結串列還提供了其它操作。其中,查詢指定的key與複製整個list依賴於迭代器的使用,並使用到自定義的比較/複製方法。

  除此之外,還提供了類似隨機讀取的方式,其內部實現為遍歷,且“越界”時返回NULL。同時,它支援index為負數,表示從尾開始。類似旋轉的操作,把尾節點移至原頭節點之前,成為新的頭節點。當然,還有拼接兩個連結串列的操作。

拷貝連結串列:

 1 /**
 2  * Duplicate the whole list. On out of memory NULL is returned.
 3  * 複製整個列表。 記憶體不足時返回 NULL。
 4  * On success a copy of the original list is returned.
 5  * 成功後,將返回原始列表的副本。
 6  *
 7  * The 'Dup' method set with listSetDupMethod() function is used
 8  * to copy the node value. Otherwise the same pointer value of
 9  * the original node is used as value of the copied node.
10  * 使用 listSetDupMethod() 函式設定的 'Dup' 方法用於複製節點值。 否則,原始節點的相同指標值將用作複製節點的值。
11  * 也就是直接將原始連結串列中節點的值拷貝一份,然後重新建立一個連結串列節點插入新連結串列的尾部
12  *
13  * The original list both on success or error is never modified.
14  * 不論拷貝結果是否成功,原始連結串列都不會被修改,執行的是深拷貝
15  *  */
16 list *listDup(list *orig)
17 {
18     list *copy;
19     listIter iter;
20     listNode *node;
21 
22     if ((copy = listCreate()) == NULL)
23         return NULL;
24     copy->dup = orig->dup;
25     copy->free = orig->free;
26     copy->match = orig->match;
27     listRewind(orig, &iter);
28     while((node = listNext(&iter)) != NULL) {
29         void *value;
30 
31         if (copy->dup) {
32             value = copy->dup(node->value);
33             if (value == NULL) {
34                 listRelease(copy);
35                 return NULL;
36             }
37         } else
38             value = node->value;
39         if (listAddNodeTail(copy, value) == NULL) {
40             listRelease(copy);
41             return NULL;
42         }
43     }
44     return copy;
45 }

根據節點值(key)得到連結串列節點:

 1 /* Search the list for a node matching a given key.
 2  * The match is performed using the 'match' method
 3  * set with listSetMatchMethod(). If no 'match' method
 4  * is set, the 'value' pointer of every node is directly
 5  * compared with the 'key' pointer.
 6  *
 7  * On success the first matching node pointer is returned
 8  * (search starts from head). If no matching node exists
 9  * NULL is returned.
10  *
11  * 返回與指定值key相匹配的從頭開始的第一個連結串列節點
12  *  */
13 listNode *listSearchKey(list *list, void *key)
14 {
15     listIter iter;
16     listNode *node;
17 
18     listRewind(list, &iter);
19     while((node = listNext(&iter)) != NULL) {
20         if (list->match) {
21             if (list->match(node->value, key)) {
22                 return node;
23             }
24         } else {
25             if (key == node->value) {
26                 return node;
27             }
28         }
29     }
30     return NULL;
31 }

根據節點位置得到連結串列節點:

 1 /* Return the element at the specified zero-based index
 2  * where 0 is the head, 1 is the element next to head
 3  * and so on. Negative integers are used in order to count
 4  * from the tail, -1 is the last element, -2 the penultimate
 5  * and so on. If the index is out of range NULL is returned.
 6  * 返回指定的索引的節點,索引值index可以為負數,-1代表尾節點
 7  *  */
 8 listNode *listIndex(list *list, long index) {                                          
 9     listNode *n;
10 
11     if (index < 0) {
12         index = (-index)-1;
13         n = list->tail;
14         while(index-- && n) n = n->prev;
15     } else {
16         n = list->head;
17         while(index-- && n) n = n->next;
18     }
19     return n;
20 }
將尾節點調整尾頭節點:
 1 /**
 2  * Rotate the list removing the tail node and inserting it to the head.
 3  * 將尾節點調整尾頭節點
 4  */
 5 void listRotate(list *list) {
 6     listNode *tail = list->tail;
 7 
 8     //如果連結串列的長度小於等1
 9     if (listLength(list) <= 1) return;
10 
11     /* Detach current tail */
12     list->tail = tail->prev;
13     list->tail->next = NULL;
14     /* Move it as head */
15     list->head->prev = tail;
16     tail->prev = NULL;
17     tail->next = list->head;
18     list->head = tail;
19 }
合併兩個連結串列:
 1 /** Add all the elements of the list 'o' at the end of the
 2  * list 'l'. The list 'other' remains empty but otherwise valid.
 3  * 將連結串列o合併到連結串列l的後面
 4  */
 5 void listJoin(list *l, list *o) {
 6     if (o->head)
 7         o->head->prev = l->tail;
 8 
 9     if (l->tail)
10         l->tail->next = o->head;
11     else
12         l->head = o->head;
13 
14     if (o->tail) l->tail = o->tail;
15     l->len += o->len;
16 
17     /* Setup other as an empty list. */
18     o->head = o->tail = NULL;
19     o->len = 0;
20 }

參考文章:

https://www.cnblogs.com/chinxi/p/12233306.html