redis 5.0.2 原始碼閱讀——雙向連結串列
阿新 • • 發佈:2021-07-04
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