Redis底层数据结构之链表

Redis底层数据结构之链表

一、Redis中链表的实现

我们都知道在列表的插入与删除的操作,如果数组的中间插入一个元素,那么这个元素后的所有元素的内存地址都要往后移动。删除的话同理,只有对数据的最后一个元素进行插入删除操作时,才比较快。链表并不需要更改节点的内存地址,链表的优势在于增和删,查找时间复杂度为O(n),链表的扩展性比数组好。链表作为一种重要的数据结构广泛用于实现redis的各种功能,由于在数据结构/算法中很多时候都学过链表,这里就不啰嗦了,直接上代码;

Redis中对链表数据结构的定义在Adlist.h文件中listNode结构体,具体的结果如下:


    /* Node, List, and Iterator are the only data structures used currently. */
    /* listNode结点 */
    typedef struct listNode {
        //结点的前一结点
        struct listNode *prev;
        //结点的下一结点
        struct listNode *next;
        //节点的值
        void *value;
    } listNode;

这是链表节点的基本定义,但是为了实现链表的各项操作,方便用户调用,Redis又做了一层封装,使用list来持有链表,具体的结构定义如下:

/* listNode 列表 */
    typedef struct list {
        //链表头结点
        listNode *head;
        //链表尾结点
        listNode *tail;

        /* 下面3个指针函数为所有结点公用的方法,相当于我们在面向对象编程中类的方法*/
        // 复制链表节点所保存的值
        void *(*dup)(void *ptr);
        // 释放链表节点所保存的值
        void (*free)(void *ptr);
        // 匹配两个节点的值是否相等
        int (*match)(void *ptr, void *key);
        // 链表长度
        unsigned long len;
    } list;

还有一个数据结构是迭代器,熟悉C++ STL或者Java集合的,应该再熟悉不过了,迭代器就要是封装集合(这里是链表)的遍历规则,让用户不必拘泥于遍历细节,下面是Redis中链表的结构定义

    /* list迭代器,只能为单向 */
    typedef struct listIter {
        //当前迭代位置的下一结点
        listNode *next;
        //迭代器的方向
        int direction;
    } listIter;

列举完链表的数据结构完之后,下面我们重点挑几个函数配合代码讲解链表的具体实现过程,首先看查找函数listSearchKey,

/* Search the list for a node matching a given key.
     * The match is performed using the 'match' method
     * set with listSetMatchMethod(). If no 'match' method
     * is set, the 'value' pointer of every node is directly
     * compared with the 'key' pointer.
     *
     * On success the first matching node pointer is returned
     * (search starts from head). If no matching node exists
     * NULL is returned. */
    /* 查找链表中是否存在值为key的节点,存在则返回改节点,否则返回NULL */
    listNode *listSearchKey(list *list, void *key)
    {
        // 这里用到了迭代器,接下来我们可以看到时如何迭代遍历的
        listIter *iter;
        listNode *node;

        //获取迭代器,遍历方向是从head往后
        iter = listGetIterator(list, AL_START_HEAD);
        //遍历循环
        while((node = listNext(iter)) != NULL) {
           //如果list定义了match方法,则调用match方法比较节点的值
            if (list->match) {
                if (list->match(node->value, key)) {
                    //如果函数返回true,则代表找到结点,释放迭代器空间,返回找到的节点
                    listReleaseIterator(iter);
                    return node;
                }
            } else {
                //如果没有定义list 的match方法,则直接比较函数指针
                if (key == node->value) {
                    //如果相等,则代表找到结点,释放迭代器
                    listReleaseIterator(iter);
                    return node;
                }
            }
        }
        listReleaseIterator(iter);
        return NULL;
    }

接下我们再看一个函数的实现,头插法加入节点listAddNodeHead

/* Add a new node to the list, to head, contaning the specified 'value'
     * pointer as value.
     *
     * On error, NULL is returned and no operation is performed (i.e. the
     * list remains unaltered).
     * On success the 'list' pointer you pass to the function is returned. */
     /* 头插法加入节点 */
    list *listAddNodeHead(list *list, void *value)
    {
        listNode *node;
        //定义新的listNode,并赋值函数指针
        if ((node = zmalloc(sizeof(*node))) == NULL)
            return NULL;
        node->value = value;
        if (list->len == 0) {
            //当此时没有任何结点时,头尾结点是同一个结点,前后指针为NULL
            list->head = list->tail = node;
            node->prev = node->next = NULL;
        } else {
            //设置此结点next与前头结点的位置关系
            node->prev = NULL;
            node->next = list->head;
            list->head->prev = node;
            list->head = node;
        }
        //结点计数递增并返回
        list->len++;
        return list;
    }

链表其他的函数,这里就不一一分析了,有兴趣的朋友,把Redis源码,下载下来,一看便知。

下面来总结下Redis采用双端链表实现的好处有哪些:

  • 双端链表可实现在O(1)的实践复杂度访问前后节点
  • 无环,表头的前驱节点和表尾的后继节点指向NULL
  • 带表头和表尾节点,程序获取表头和表尾的时间复杂度为O(1)
  • 使用链表长度计数器,可在O(1)的时间复杂度内获取链表长度
  • 链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值