最新redis底层数据结构之双向链表

一)简介

        双向链表是redis中数据类型List的底层数据结构之一,双向链表也是一种常见的数据结构,在redis中也是大同小异 主要包括三个元素分别为链表,节点,链表迭代器,其设计比较简单 api易懂 为了方便大家阅读 本文做了详细的源码介绍 ,可以收藏下来。


二)性能优化分析

  • 节点的双向指针prev和next,方便通过迭代器从头至尾或从尾至头遍历。
  • 链表的头尾指针head和tail,对链表的头尾插入,以及头尾节点读取,高效地实现list的一些指令(如push 和pop相关指令)。
  • 链表的长度len,得计算链表长度的时间复杂度为O(1),不需要遍历整个链表。

三)来源

        以下源码均来自redis 6.0.12版本的 adlist.h和adlist.c文件。


四)数据结构类型定义

/* 双向链表中的节点 */
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;// 链表尾节点
        // 定义三个函数指针  为什么要定义这三个函数指针?因为listnode中的数据区域为一个void类型的指针(类似java中泛型),
        //所指向的结构可能千差万别,而且这些内存需要手动释放。将常用的这几个函数定义在这里,可以在需要的时候直接回调。
    
    void *(*dup)(void *ptr);//复制节点值
    void (*free)(void *ptr);//释放节点值
    int (*match)(void *ptr, void *key);///匹配节点值是否相等
    unsigned long len;    // 链表长度
} list;


五)宏定义

宏名称作用
listLength获取链表的长度值
listFirst获取链表的首指针
listLast获取链表的尾指针
listPrevNode获取当前节点的前驱节点指针
listNextNode获取当前节点的后继节点指针
listNodeValue获取当前节点所存储的值
listSetDupMethod设置链表节点value的复制函数
listSetFreeMethod设置链表节点value的释放内存函数
listSetMatchMethod设置链表节点value的比较函数
listGetDupMethod获取链表节点value的复制函数
listGetFree获取链表节点value的释放内存函数
listGetMatchMethod获取链表节点value的比较函数


六)API简介

函数参数定义复杂度
list *listCreate(void);创建一个listO(1)
void listEmpty(list *list);要清空元素的链表在不破坏链表本身的情况下,从链表中删除所有元素O(N)
void listRelease(list *list);要释放的链表释放链表,清空链表然后释放链表本身的空间O(N)
list *listAddNodeHead(list *list, void *value);链表,添加节点的值再链表头部添加一个节点O(1)
list *listAddNodeTail(list *list, void *value);链表,添加节点的值再链表尾部添加一个节点O(1)
list *listInsertNode(list *list, listNode *old_node, void *value, int after);链表,指定节点,添加节点的值,前或者后在链表的指定节点前或者后添加一个节点O(1)
void listDelNode(list *list,listNode *node);链表,指定节点删除指定的节点O(1)
listIter *listGetIterator(list *list, int direction);链表,迭代方向生成双向链表的迭代器O(1)
void listReleaseIterator(listIter *iter);指定迭代器释放双向链表的迭代器O(1)
listNode *listNext(listIter *iter);指定迭代器通过迭代器获取下一个节点O(1)
list *listDup(list *orig);要被复制的链表创建指定链表的副本O(N)
listNode *listSearchKey(list *list, void *key);链表,指定的值查找与指定key相同值的节点O(N)
listNode *listIndex(list *list, long index);链表,索引值根据给定的索引值,返回相应的节点O(N)
void listRewind(list *list, listIter *li);链表,迭代器重新初始化迭代器,迭代方向从头至尾O(1)
void listRewindTail(list *list, listIter *li);链表,迭代器重新初始化迭代器,迭代方向从尾至头O(1)
void listRotateTailToHead(list *list);链表将链表的尾节点移到链表的头节点O(1)
void listRotateHeadToTail(list *list);链表将链表的头节点移到链表的尾节点O(1)
void listJoin(list *l,list *o);合并链表,被合并链表将两个链表合并o链表合并到l链表后面O(1)


七)API源码分析


    listCreate

//创建一个新链表。创建的链表可以使用listRelease()释放,但是每个节点的私有值需要用户在调用listRelease()之前释放,或者使用listSetFreeMethod设置一个free方法。出现错误时,返回NULL。否则指向新列表的指针。
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;
}
//创建一个新链表。

    listEmpty

/* 在不破坏链表本身的情况下,从链表中删除所有元素. */
void listEmpty(list *list)
{
    unsigned long len;
    listNode *current, *next;

    current = list->head;
    len = list->len;
    while(len--) {
        next = current->next;
        //通过list中的回调函数来释放每一节点数据域的内存空间
        if (list->free) list->free(current->value);
        zfree(current);//销毁节点空间,zfree是redis定义的空间释放函数,同zmalloc
        current = next;
    }
    //清空后链表只是变成空链表 空间还占着
    list->head = list->tail = NULL;
    list->len = 0;
}

    listRelease

//释放整个链表且这个函数不会失败
//注:和listEmpty不同 清空链表后还把链表本身空间也释放
void listRelease(list *list)
{
    listEmpty(list);
    zfree(list);
}

    listAddNodeHead

//向列表头部添加一个新节点,该节点将指定的'value'指针作为value。
//出错时,返回NULL,不执行任何操作(即列表保持不变)。
//如果成功,将返回传递给函数的'list'指针
list *listAddNodeHead(list *list, void *value)
{
    listNode *node;
    //主要时分配节点空间时会出错 则返回null
    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    //如果链表没节点,就将node作为头和尾节点并把前后指针指向null
    node->value = value;
    if (list->len == 0) {
        list->head = list->tail = node;
        node->prev = node->next = NULL;
    } else {
        //如果链表有节点,就将node前指针指向null 后指针指向之前链表的头节点
        node->prev = NULL;
        node->next = list->head;
        //改变之前头节点为当前节点
        list->head->prev = node;
        list->head = node;
    }
    list->len++;//长度加1
    return list;
}

    listAddNodeTail

//原理同listAddNodeHead 就是指针指向相反
list *listAddNodeTail(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 = list->tail;
        node->next = NULL;
        list->tail->next = node;
        list->tail = node;
    }
    list->len++;
    return list;
}

    listInsertNode

list *listInsertNode(list *list, listNode *old_node, void *value, int after) {
    listNode *node;

    if ((node = zmalloc(sizeof(*node))) == NULL)
        return NULL;
    node->value = value;
    if (after) {
        //在后面增加节点
        node->prev = old_node;
        node->next = old_node->next;
        if (list->tail == old_node) {//改变链表尾部节点
            list->tail = node;
        }
    } else {
        //在前面增加节点
        node->next = old_node;
        node->prev = old_node->prev;
        if (list->head == old_node) {//改变链表头部节点
            list->head = node;
        }
    }
    //如果前节点不为空,就把前节点的后节点指向node (确保双向连接)
    if (node->prev != NULL) {
        node->prev->next = node;
    }
    //如果后节点不为空,就把后节点的前节点指向node (确保双向连接)
    if (node->next != NULL) {
        node->next->prev = node;
    }
    list->len++; //长度加1
    return list;
}

    listDelNode

//从指定列表中删除指定的节点。
//由调用者释放节点的私有值。
//这个函数不会出错
void listDelNode(list *list, listNode *node)
{
    if (node->prev)
        node->prev->next = node->next;//node不是头节点,就把node的前节点的后指针指向node的后节点
    else
        list->head = node->next;//node是头节点,就把node的后节点作为链表的头节点
    if (node->next)
        node->next->prev = node->prev;//node不是尾节点,就把node的后节点的前指针指向node的前节点
    else
        list->tail = node->prev;//node是尾节点,就把node的前节点作为链表的尾节点
    if (list->free) list->free(node->value);//释放节点值
    zfree(node);//释放节点空间
    list->len--;//总长度减1
}

    listGetIterator

//返回列表迭代器 'iter'。初始化之后,每次调用listNext()都会返回列表的下一个元素。
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;
}

    listReleaseIterator

/* 释放指定迭代器的空间 */
void listReleaseIterator(listIter *iter) {
    zfree(iter);
}

    listNext

//返回迭代器的下一个节点
//可以使用listDelNode()删除当前返回的元素,但不能删除其他元素。
//该函数返回指向列表中下一个元素的指针,如果没有其他元素,则返回NULL
 //以下是经典的使用示例:
 // iter = listGetIterator(list,<direction>);
 // while ((node = listNext(iter)) != NULL) {
 //     doSomethingWith(listNodeValue(node));
 // }
 
listNode *listNext(listIter *iter)
{
    //根据迭代方向依次遍历节点,并返回当前节点
    listNode *current = iter->next;
    if (current != NULL) {
        if (iter->direction == AL_START_HEAD)
            iter->next = current->next;
        else
            iter->next = current->prev;
    }
    return current;
}

    listDup

//复制整个链表。内存不足时返回NULL。成功时返回原链表的副本。
//通过listSetDupMethod()函数设置的'Dup'方法用于复制节点值,否则将使用原节点的相同指针值作为复制节点的值。
//不管结果成功还是失败,原来的链表都不会改变 
list *listDup(list *orig)
{
    list *copy;
    listIter iter;
    listNode *node;
    //创建新链表
    if ((copy = listCreate()) == NULL)
        return NULL;
    //复制相关回调函数
    copy->dup = orig->dup;
    copy->free = orig->free;
    copy->match = orig->match;
    listRewind(orig, &iter);//重置迭代器
    //遍历迭代器 
    while((node = listNext(&iter)) != NULL) {
        void *value;
        //复制值
        if (copy->dup) {
            value = copy->dup(node->value);
            if (value == NULL) {
                listRelease(copy);
                return NULL;
            }
        } else
            value = node->value;
        //添加尾部节点失败(如空间分配不够失败) 就释放整个链表copy 不再继续遍历下去
        if (listAddNodeTail(copy, value) == NULL) {
            listRelease(copy);
            return NULL;
        }
    }
    return copy;
}

    listSearchKey

//在链表中搜索与给定键匹配的节点。
//匹配是使用listSetMatchMethod()设置的'match'方法执行的。
//如果没有设置'match'方法,则每个节点的'value'值将直接与'key'值比较
 //成功时返回第一个匹配的节点(搜索从head开始),如果不存在匹配的节点,则返回 NULL。
listNode *listSearchKey(list *list, void *key)
{
    listIter iter;
    listNode *node;

    listRewind(list, &iter);
    //遍历迭代器 match上了就返回节点
    while((node = listNext(&iter)) != NULL) {
        if (list->match) {
            if (list->match(node->value, key)) {
                return node;
            }
        } else {
            if (key == node->value) {
                return node;
            }
        }
    }
    return NULL;
}

    listIndex

//返回指定的从零开始的索引处的元素,其中0是头。
//使用负整数是为了从尾部开始计数*,-1是最后一个元素,-2是倒数第二个*,以此类推。
//如果索引超出范围将返回NULL
listNode *listIndex(list *list, long index) {
    listNode *n;
    //索引值为负数时,从尾部遍历
    if (index < 0) {
        index = (-index)-1;
        n = list->tail;
        while(index-- && n) n = n->prev;
    } else {
        //索引值为正数时,从头部遍历
        n = list->head;
        while(index-- && n) n = n->next;
    }
    return n;
}

    listRewind

void listRewind(list *list, listIter *li) {
    //把迭代器的后指针和方向设置即可
    li->next = list->head;
    li->direction = AL_START_HEAD;
}

    listRewindTail

//同listRewind 刚好相反
void listRewindTail(list *list, listIter *li) {
    li->next = list->tail;
    li->direction = AL_START_TAIL;
}

    listRotateTailToHead

//旋转链表,移除尾部节点并将其插入头部
void listRotateTailToHead(list *list) {
    if (listLength(list) <= 1) return;

    //分离当前尾节点 并设置新的尾节点
    listNode *tail = list->tail;
    list->tail = tail->prev;
    list->tail->next = NULL;
    //将分离尾节点插入链表头部,做为新的头节点
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;
    list->head = tail;
}

    listRotateHeadToTail

//旋转链表,移除头部节点并将其插入尾部
void listRotateHeadToTail(list *list) {
    if (listLength(list) <= 1) return;

    listNode *head = list->head;
    //分离当前头节点 并设置新的头节点
    list->head = head->next;
    list->head->prev = NULL;
    //将分离头节点插入链表尾部,做为新的尾节点
    list->tail->next = head;
    head->next = NULL;
    head->prev = list->tail;
    list->tail = head;
}

    listJoin

//将链表'o'中的所有元素添加到链表'l'的末尾
void listJoin(list *l, list *o) {
    //将o链表头节点指向链表l的尾节点
    if (o->head)
        o->head->prev = l->tail;
    //将l链表尾节点指向链表o的头节点
    if (l->tail)
        l->tail->next = o->head;
    else
        l->head = o->head;

    if (o->tail) l->tail = o->tail;
    l->len += o->len;

    //将o链表设置为空链表
    o->head = o->tail = NULL;
    o->len = 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis是一个开源的内存数据库,它使用了多种数据结构来存储不同类型的数据。下面是几种常见的Redis底层数据结构的详解: 1. 字符串(String):字符串是Redis中最基本的数据结构。它可以存储任意类型的数据,包括数字、文本等。字符串在Redis中以字节数组的形式存储,可以通过键访问和修改。 2. 列表List):列表是一个有序的字符串集合,可以在列表的两端进行插入、删除和获取操作。Redis使用双向链表来实现列表数据结构,它支持快速插入和删除操作。 3. 哈希(Hash):哈希是一种键值对的集合。在Redis中,哈希可以存储多个字段和对应的值,类似于关联数组或者字典。哈希在内部使用哈希表来实现,可以快速查找和修改字段值。 4. 集合(Set):集合是一组唯一且无序的字符串集合。Redis使用哈希表来实现集合数据结构,它支持添加、删除和判断元素是否存在等操作。 5. 有序集合(Sorted Set):有序集合是一组唯一且有序的字符串集合。在Redis中,每个元素都会关联一个分数,通过分数可以对元素进行排序。有序集合的实现使用了跳跃表和哈希表两种数据结构,它支持添加、删除、修改和范围查询等操作。 这些数据结构底层实现都是高效的,并且支持丰富的操作。Redis数据结构灵活性较高,能够满足不同类型的数据存储需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值