Redis's two-way linked list Library is a very standard, textbook-like library, relative to the previously introduced dictionary and SDS string Libraries. But as part of the Redis source code, I decided to talk about it. (reprint please indicate CSDN blog for breaksoftware)
Basic structure
First we look at the structure of the linked list element. Because it is a doubly linked list, its base element should have a pointer to the previous node, a pointer to the next node, and a space to record the value of the node
typedef struct listNode {
struct listNode * prev;
struct listNode * next;
void * value;
} listNode;
Because a doubly linked list can be traversed not only from the beginning, but also from the tail, the linked list structure should have at least pointers to the head and tail nodes.
But as a linked list that can carry various types of data, users of the linked list also need to provide some ability to process the data in the node. Because these data may be user-defined, operations such as copying, deleting, and comparing require users to tell the framework. In the article "Analysis of Redis Source Code-Dictionary Structure", we see that the dictType structure that users need to pass in when creating a dictionary is a carrier that carries the above processing method of data. But Redis did not use a structure to carry these methods when designing a doubly linked list, but defined it in the linked list structure
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;
As for why the length len of the linked list is stored in the linked list structure, I feel that it is not necessary in terms of necessity. One advantage of having a len field is that you do n’t have to do a traversal operation every time you calculate the length of the linked list. The disadvantage is that the export needs to maintain this variable.
Create and release linked lists
The process of creating a linked list is relatively simple. Just applied for a list structure space, and then set each field to NULL
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;
}
But what is more interesting is that the list type is not set when creating the linked list-there is no pointer to copy, release, compare and other methods. The author separately provided some macros to set, personally feel that this split design is not very good
#define listSetDupMethod (l, m) ((l)-> dup = (m))
#define listSetFreeMethod (l, m) ((l)-> free = (m))
#define listSetMatchMethod (l, m) ((l)-> match = (m))
The operation of releasing the linked list is to release the nodes one by one from the head to the tail. The iterative method is to determine whether the length len of the decreasing linked list length is 0. As mentioned before, len is actually not necessary, as long as the next pointer of the node is judged to be empty, it will be known to the end.
void listRelease (list * list)
{
unsigned long len;
listNode * current, * next;
current = list-> head;
len = list-> len;
while (len--) {
next = current-> next;
if (list-> free) list-> free (current-> value);
zfree (current);
current = next;
}
zfree (list);
}
New Nodes There are three types of new nodes: new head, new tail and middle. The addition of the head and tail is very simple, just need to consider whether the linked list is empty before adding. If it is empty, set the pointers of the newly added nodes to NULL, and make the head and tail pointers of the linked list point to the newly added nodes.
list * listAddNodeHead (list * list, void * value)
{
listNode * node;
if ((node = zmalloc (sizeof (* node))) == NULL)
return NULL;
node-> value = value;
if (list-> len == 0) {
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-> len ++;
return list;
}
list * listAddNodeTail (list * list, void * value)
{
listNode * node;
if ((node = zmalloc (sizeof (* node))) == NULL)
return NULL;
node-> value = value;
if (list-> len == 0) {
list-> head = list-> tail = node;
node-> prev = node-> next = NULL;
} else {
node-> prev = list-> tail;
node-> next = NULL;
list-> tail-> next = node;
list-> tail = node;
}
list-> len ++;
return list;
}
The above code also illustrates a problem. The data pointer of the newly created node points to the content of the incoming value, and the data pointed to by value is not copied using a copy operation. Therefore, the data inserted into the linked list must be valid within the lifetime of the linked list.
When inserting a node in the middle of the linked list, you can specify which node to insert before or after. In this scenario, it is necessary to consider whether the node as the coordinate is the head node or tail node of the linked list; if so, the head and tail nodes of the linked list may be updated depending on the position of the newly inserted node.
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-> prev = old_node;
node-> next = old_node-> next;
if (list-> tail == old_node) {
list-> tail = node;
}
} else {
node-> next = old_node;
node-> prev = old_node-> prev;
if (list-> head == old_node) {
list-> head = node;
}
}
if (node-> prev! = NULL) {
node-> prev-> next = node;
}
if (node-> next! = NULL) {
node-> next-> prev = node;
}
list-> len ++;
return list;
}
Delete a node
When deleting a node, consider whether the node is the head node or tail node of the linked list. If it is, then update the information of the linked list, otherwise only update the node pointing relationship before and after the node to be deleted.
void listDelNode (list * list, listNode * node)
{
if (node-> prev)
node-> prev-> next = node-> next;
else
list-> head = node-> next;
if (node-> next)
node-> next-> prev = node-> prev;
else
list-> tail = node-> prev;
if (list-> free) list-> free (node-> value);
zfree (node);
list-> len--;
}
Create and release iterators
An iterator is a structure that assists in traversing a linked list. It is divided into forward iterators and backward iterators. We can find the direction type variable in the iterator structure
typedef struct listIter {
listNode * next;
int direction;
} listIter;
Create an iterator, you need to specify the direction, so that the next pointer of the iterator can point to the head node or tail node of the linked list
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;
}
You can also change the iterator type through the following two methods, for example, you can make a forward iterator into a backward iterator. You can also make this iterator point to another linked list instead of the linked list when you created it.
void listRewind (list * list, listIter * li) {
li-> next = list-> head;
li-> direction = AL_START_HEAD;
}
void listRewindTail (list * list, listIter * li) {
li-> next = list-> tail;
li-> direction = AL_START_TAIL;
}
Because the iterator created by listGetIterator is dynamically allocated on the heap, it should be released when not in use
void listReleaseIterator (listIter * iter) {
zfree (iter);
}
Iterator traversal Iterator traversal is actually a simple process of accessing the next node through the node forward and backward pointers
listNode * listNext (listIter * iter)
{
listNode * current = iter-> next;
if (current! = NULL) {
if (iter-> direction == AL_START_HEAD)
iter-> next = current-> next;
else
iter-> next = current-> prev;
}
return current;
}
Linked list copy The linked list copy process is to copy the data in the meta-linked list to the newly created linked list through an iterator that is accessed from beginning to end. But there is one thing to note here is that the copy operation may be divided into deep copy and shallow copy. If we set the data copy method through listSetDupMethod, then use this method to copy the data, and then put the copied new data into the new linked list. If it is not set, it simply assigns the value field of the elements in the old list.
list * listDup (list * orig)
{
list * copy;
listIter iter;
listNode * node;
if ((copy = listCreate ()) == NULL)
return NULL;
copy-> dup = orig-> dup;
copy-> free = orig-> free;
copy-> match = orig-> match;
listRewind (orig, & iter);
while ((node = listNext (& iter))! = NULL) {
void * value;
if (copy-> dup) {
value = copy-> dup (node-> value);
if (value == NULL) {
listRelease (copy);
return NULL;
}
} else
value = node-> value;
if (listAddNodeTail (copy, value) == NULL) {
listRelease (copy);
return NULL;
}
}
return copy;
}
Finding elements Finding elements also traverses the entire linked list through an iterator, and then depends on whether the user sets a comparison method through listSetMatchMethod to decide whether to use a user-defined method to compare or directly use value to compare. If you use value to directly compare, it is a strong comparison, that is, the comparison data and the linked list data are in the same location in memory.
listNode * listSearchKey (list * list, void * key)
{
listIter iter;
listNode * node;
listRewind (list, & iter);
while ((node = listNext (& iter))! = NULL) {
if (list-> match) {
if (list-> match (node-> value, key)) {
return node;
}
} else {
if (key == node-> value) {
return node;
}
}
}
return NULL;
}
Accessing the linked list by subscript The subscript can be a negative number, which means to return the number of elements from the bottom.
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;
}
The end node is moved forward as the head node. This method is used more in Redis code. It moves the last node of the linked list to the head of the linked list. I want to design such a method so that the contents of the linked list can be evenly polled in the case of stateless records.
void listRotate (list * list) {
listNode * tail = list-> tail;
if (listLength (list) <= 1) return;
/ * Detach current tail * /
list-> tail = tail-> prev;
list-> tail-> next = NULL;
/ * Move it as head * /
list-> head-> prev = tail;
tail-> prev = NULL;
tail-> next = list-> head;
list-> head = tail;
}
Redis source code analysis-doubly linked list