redis双向链表源码解析(adlist.h/adlist.c)

redis双向链表源码解析(adlist.h/adlist.c)

数据结构

listNode是一个双向链表节点结构,每个节点存的数据用void*指向。

listIter是一个双向链表迭代器,其中direction用来判断迭代方向

list表示双向链表结构,保存着头节点,尾节点,提供三个函数指针, 供用户传入自定义函数, 用于复制(dup)、释放(free)和匹配(match)链表中的结点的值(value); 通过无符号长整数len, 标示链表的长度。

/*
 * 双端链表节点
 */
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;

创建list函数listCreate

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:listRelease

void listRelease(list *list)
{
    unsigned long 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);
}

在释放节点时,如果有设置释放函数,则使用释放函数释放,该函数没有返回值

list添加一个节点到Head:listAddNodeHead

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;
}

注意:添加到非空链表时,是添加到链表的头部,所以list->head需要有所调整。

list添加一个节点到Head:listAddNodeTail

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;
}

添加节点别忘了改变list->list

添加节点到中间listInsertNode(复杂度o(1))

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;
        }
    }

    // 更新新节点的前置指针
    if (node->prev != NULL) {
        node->prev->next = node;
    }
    // 更新新节点的后置指针
    if (node->next != NULL) {
        node->next->prev = node;
    }

    // 更新链表节点数
    list->len++;

    return list;
}

该函数是个入参1.list 2.标记节点 3.插入的value 4.after,是插入标记的前面还是后面

删除节点listDelNode

void listDelNode(list *list, listNode *node)
{
    // 调整前置节点的指针
    if (node->prev)
        node->prev->next = node->next;
    else
        list->head = node->next;

    // 调整后置节点的指针
    if (node->next)
        node->next->prev = node->prev;
    else
        list->tail = node->prev;

    // 释放值
    if (list->free) list->free(node->value);

    // 释放节点
    zfree(node);

    // 链表数减一
    list->len--;
}

注意需要判断一下list是否为空。如果有释放函数就调释放函数,如果没有,就使用zfree.

创建迭代器:listGetIterator

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;
}

1.为其分配内存 2.根据迭代器方向,选择迭代器next的指向 3.返回迭代器指针

复制整个list : listDup

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;

    // 迭代整个输入链表
    iter = listGetIterator(orig, AL_START_HEAD);
    while((node = listNext(iter)) != NULL) 
    {
        void *value;
        // 复制节点值到新节点
        if (copy->dup) 
        {
            value = copy->dup(node->value);
            if (value == NULL) 
            {
                listRelease(copy);
                listReleaseIterator(iter);
                return NULL;
            }
        } 
        else
            value = node->value;
        // 将节点添加到链表
        if (listAddNodeTail(copy, value) == NULL) 
        {
            listRelease(copy);
            listReleaseIterator(iter);
            return NULL;
        }
    }
    // 释放迭代器
    listReleaseIterator(iter);
    // 返回副本
    return copy;
}

这里面调用了多个前面描述过的函数listCreate,listGetIterator,listNext,listRelease,listReleaseIterator,listAddNodeTail,这段代码看起来也相对轻松。并且用到了指针函数dup

查找list与key匹配的节点

listNode *listSearchKey(list *list, void *key)
{
    listIter *iter;
    listNode *node;
    // 迭代整个链表
    iter = listGetIterator(list, AL_START_HEAD);
    while((node = listNext(iter)) != NULL) 
    {
        // 对比
        if (list->match)
        {
            if (list->match(node->value, key)) 
            {
                listReleaseIterator(iter);
                // 找到
                return node;
            }
        } 
        else
        {
            if (key == node->value)
            {
                listReleaseIterator(iter);
                // 找到
                return node;
            }
        }
    }
    
    listReleaseIterator(iter);

    // 未找到
    return NULL;
}

list->match(node->value, key)调用match匹配函数,比较value与key,如果找到了,就返回当前节点指针。

索引函数:listIndex

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;
}

需要注意的是,该函数接受负数,索引从0开始

使用方法

Redis定义了一系列的宏,用于访问list及其内部结点。

链表创建时(listCreate), 通过Redis自己实现的zmalloc()分配堆空间。 链表释放(listRelease)或删除结点(listDelNode)时, 如果定义了链表(list)的指针函数free, Redis会使用它释放链表的每一个结点的值(value), 否则需要用户手动释放。 结点的内存使用Redis自己实现的zfree()释放。

对于迭代器, 通过方法listGetIterator()、listNext()、 listReleaseIterator()、listRewind()和listRewindTail()使用, 例如对于链表list,要从头向尾遍历,可通过如下代码:

iter = listGetIterator(list, AL_START_HEAD); // 获取迭代器
while((node = listNext(iter)) != NULL)
{
        dosomething;
}
listReleaseIterator(iter);

listDup()用于复制链表, 如果用户实现了dup函数, 则会使用它复制链表结点的value。 listSearchKey()通过循环的方式在O(N)的时间复杂度下查找值, 若用户实现了match函数, 则用它进行匹配, 否则使用按引用匹配。

如果你对链表比较熟悉,看这段代码应该很简单,如果不了解数据机构链表,可以先学习一下链表,然后学习redis的list源码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值