Redis定义了双向链表,用来存储列表键的值,还有其他的我不知的。
双向链表的节点定义为:
typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 节点的值
void *value;
} listNode;
这里是把节点的值作为了一个成员变量,Linux内核里面节点的定义是这样的
struct list_head{
struct list_head *next;
struct list_head *prev;
}
这样可以没有不用处理节点所带数据的类型,但是要通过offsetof/container_of这类来处理。不过这是题外话了。
除此之外,Redis还定义了链表结构,如下:
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;
这样定义的好处有:
快速获取链表的头尾
快速查询链表的节点数,像动态字符串sdshr里的len那样,不用遍历全部字符就可以获取字符串的长度,这里也类似。
它里面还定义了链表的迭代器,
typedef struct listIter {
// 当前迭代到的节点
listNode *next;
// 迭代的方向
int direction;
} listIter;
里面迭代的方向为
// 从表头向表尾进行迭代
#define AL_START_HEAD 0
// 从表尾到表头进行迭代
#define AL_START_TAIL 1
迭代器可以通过以下几个接口来使用(不完全列出来)
listIter *listGetIterator(list *list, int direction) //根据迭代方向给链表创建迭代器
listNode *listNext(listIter *iter) //返回迭代器当前指向的节点,并且把迭代器根据方向向前移动一下
假如我要遍历整个链表,我可以这样做
iter = listGetIterator(list, AL_START_HEAD);
while ((node = listNext(iter)) != NULL) {
doSomethingWith(node);
}
写起来是比较简单易懂的。
里面还有个反转节点的函数
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设计与实现》里的总结
链表被广泛用于实现 Redis 的各种功能, 比如列表键, 发布与订阅, 慢查询, 监视器, 等等。
每个链表节点由一个 listNode 结构来表示, 每个节点都有一个指向前置节点和后置节点的指针, 所以 Redis 的链表实现是双端链表。
每个链表使用一个 list 结构来表示, 这个结构带有表头节点指针、表尾节点指针、以及链表长度等信息。
因为链表表头节点的前置节点和表尾节点的后置节点都指向 NULL , 所以 Redis 的链表实现是无环链表。
通过为链表设置不同的类型特定函数, Redis 的链表可以用于保存各种不同类型的值。
参考:
1.《Redis设计与实现》
2.https://github.com/huangz1990…