《redis设计与实现》第三章是讲链表的,对于像我这种使用C++的人来说,没看过源码之前只是简单地理解为std::list,看过之后发现还是可以理解为std::list,只是帮我深度复习了一次链表数据结构。
Redis整个代码是基于C语言来写的,在C++、Java中习以为常的vector、list这类寻常的数据结构需要人肉再实现一遍,总觉得写这类代码比较别扭,习惯了衣来伸手直接使用的日子,毕竟都是一些大学教科书上的东西,这次看过之后,通过罗列相关的数据结构特点,却也帮我回忆了各个知识点,略有收获。感觉上adlist有这几个特点:
1. 双向链表
2. C风格的简易迭代器
3. 开放的操作入口
adlist的主要结构体定义如下:
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct listIter {
listNode *next;
int direction;
} listIter;
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;
首先,先说说双向链表,这部分在功能上和std::list很相似:
在struct list中保存了链表的头尾指针,在listNode中则保存了前后节点的地址,再结合listIter.direction可以非常方便的选择是正向移动或者反向移动,进行定位;同时,采用了离散内存而不是连续内存实现,可以在链表的任何位置插入或者删除节点,性能上非常高。但有一点要注意,adlist中删除、添加都是直接操作节点指针而不是操作迭代器,listIter在整个链表中主要以读为主,这和std::list不同,要格外注意安全性。
其次,说说listIter这个简易迭代器,它保存了当前节点的指针以及方向,主要在以下几个函数中使用,都是以读为主的:
listIter *listGetIterator(list *list, int direction);
listNode *listNext(listIter *iter);
void listReleaseIterator(listIter *iter);
void listRewind(list *list, listIter *li);
void listRewindTail(list *list, listIter *li);
最后,说说开放的操作入口,这一特性主要特显在几个函数指针以及数据类型上,节点的数据类型是void*,意味着需要由外界使用者自己管理这块内存,同时下面这三个函数指针可以在创建链表的时候指定,实现逻辑的自定义以适配不同的节点类型:
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
总结:adlist结构比较基础,没啥好说的,简单看看即可。