设为首页 收藏本站
查看: 1286|回复: 0

[经验分享] Redis中的通用链表结构

[复制链接]

尚未签到

发表于 2015-7-20 14:11:58 | 显示全部楼层 |阅读模式
  Redis是一个正在进行中的开源键值数据库项目,作者是antirez。redis与一般键值数据库相比,其最大特色是键值对中的值支持链表、集合、排序集等复合结构。这里我们来看看antirez是怎样实现通用链表结构的。
如果你熟悉Linux,应该知道Linux内核也实现了通用链表结构,它使用了GCC中超越了ANSI C规范的特性。但是Redis没有这样做,而是使用了类似C++ STL的做法。Redis实现的是双向非循环链表,牵涉到三种数据结构:结点listNode、迭代器listIter、链表本身list。代码如下。




typedef struct listNode {
    void    *value;
    struct listNode    *prev;
    struct listNode    *next;
}listNode;
  value指向具体的数据,从后面两个指针可以看出这是个双向链表,至于是否循环要从其他代码判断。





typedef struct listIter {
    struct listNode    *next;
    int    direction;
}listIter;
  这是个迭代器,当然需要一个指针来指向结点咯,就是next;direction表示该迭代器的前进方向,AL_START_HEAD表示从头开始,AL_START_TAIL表示从尾开始。
  
好,链表结构是时候出来了


DSC0000.gif DSC0001.gif adlist.h



typedef struct list {
    listNode    *head;
    listNode    *tail;
    void* (*dup)(void *ptr);
    void (*free)(void *ptr);
    int (*match)(void *ptr, void*key);
    unsigned int len;
}list;
  head、tail和len很好理解,中间三个变量是干嘛的呢,它们是函数变量,dup是复制函数,你可以自己定义复制策略,free是释放策略,match是如何在链表中查询内容的策略。
  
与链表相关的数据结构就是这三个,下面来看看此链表的API是如何实现的,API详情可查看adlist.h文件。
  
  list* listCreate(void);  毫无疑问这是链表的构造函数


adlist.c



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;
}
  没什么好说的,从堆中动态分配一个list结构,初始化成员变量,然后返回list的指针,这样我们就能控制堆中的链表了。注意zmalloc是Redis自己封装的内存分配函数,如果感兴趣可以查看zmalloc.c文件
  
有构造函数,就自然有析构函数了,如下:
void listRelease(list *list);






void listRelease(list *list)
{
    unsigned int 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);
}
  zfree一样是Redis封装的函数,这里要注意的是list->free,它是用来释放结点中的数据的,说明如果你的listNode中的value是指向堆中的内存的话,就一定要为链表定义释放函数,否则会造成内存泄漏。
  
  现在来看看怎样为链表增加结点,Redis只提供了在链表头部或尾部添加结点的方法,可能这些对Redis已经够用了吧。
list* listAddNodeHead(list* list, void *value);


adlist.c



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;
}
  我们只需要为链表提供真正的数据value即可,该函数会自己创建listNode结构。这个函数的声明有点儿意思,它返回的是list指针,通常我们会返回int,用0表示成功,-1表示失败。而Redis中如果链表插入失败会返回NULL,原来的链表不会受影响;如果插入成功,那么就返回改变后的链表。listAddNodeTail类似。
  
  
有插入自然就有删除咯,插入时我们只提供了listNode中value,但删除就要提供listNode的地址了。与listRelease一样,如果value指向的内存在堆中,就要提供你自己定义的free函数,否则会有内存泄漏。
void listDelNode(list *list, listNode *node);


adlist.c  这个函数为什么不像插入函数一样返回list指针呢,源代码中说这个函数不可能失败,所以没必要返回值。不过我认为返回list指针也好啊,可以保持接口的一致性。不知道antirez是怎么想的,难道是考虑性能。
  
  
  Redis中是通过迭代器访问结点的,同STL一样,那么必须要有个为链表生成迭代器的函数
listIter* listGetIter(list *list, int direction);


adlist.c



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;
}
  
  
  
  迭代器不是链表的一部分,而是帮助程序员操作链表结点的,所以它需要一个单独的析构函数
void listReleaseIterator(listIter *iter);




void listReleaseIterator(listIter *iter) {
    zfree(iter);
}
  
  
  




现在我们要通过迭代器访问链表结点,这个函数或许是理解链表最重要的吧,他会返回迭代器当前所指的结点,并且根据direction前进或后退
listNode* listNext(listIter *iter);



adlist.c
对于direction==AL_START_HEAD而言,当迭代器已经指向了最后一个结点时,如果你再调用此函数,他就会返回NULL。所以我们遍历链表的框架如下:

    iter = listGetIterator(list);
    while ( (node=listNext(iter))!=NULL ) {
        doSomethingWith(listNodeValue(node));  //listNodeValue是adlist.h中的宏,从结点中取出值
    }
我觉得这个框架有点儿别扭,与STL好像不太一样,关于这个接口的设计我会在总结中谈到。  
  
  
  


总结:  


通用链表结构的基本API已经讲述完毕,想要透彻了解最好去看Redis的源码。但是从listDelNode开始,我嗅到了一丝丝的不和谐,listDelNode需要我们传一个listNode型的指针,但是在一个好的通用链表中用户的概念中应该只有三种数据结构:链表,迭代器,和与具体应用相关的value的类型。在用户的概念之中value应该就是链表中的结点,而没有必要知道value其实是listNode中的一个指针,所以在用户接口中listNode根本就不应该出现。基于这样的理念,我们可以更改两个接口:
    void listDelNode(listNode *node) 应改为 void listDelNode(listIter *iter)
    listNode* listNext(listIter *iter)应改为 listIter* listNext(listIter *iter)
这样遍历框架就成了
    iter = listGetIter(list, direction);
    while ( !listIterNull(iter) ) {
        doSomethingWith(listIterValue(iter));
        listNext(iter);
    }
看,这样遍历框架是不是顺眼多了。我们还要实现框架中的listIterNull和listIterValue,都很简单
    #define listIterNull(iter) (iter->next)
    #define listIterValue(iter) (iter->next->value)
OK,告一段落,你有没有从中领悟到一些通用数据结构设计的思想呢?

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-88775-1-1.html 上篇帖子: Redis教程03——Redis 发布/订阅(Pub/Sub) 下篇帖子: Redis Cluster原理
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表