Redis底层数据结构之链表

来源:互联网 发布:电子书什么软件好 编辑:程序博客网 时间:2024/06/07 11:41

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
原创粉丝点击