Redis研究-3.1 数据结构之链表

我们知道,数据结构中,链表的最大好处就是能高效的实现动态增、删、改,缺点就是遍历访问比较慢,因此,在Redis中,很多功能的底层实现都是基于链表的,因为Redis是基于C语言来写的,所以只能自己实现自己的链表结构。对于一个常规的双向链表节点,我们通常使用下面的方式来定义:

typedef struct Node{
   void *value;
   struct Node *prev;
   struct Node *next;
}Node; 

Redis中,在adlist.h中也是这样子定义的

typedef struct listNode {

    // 前置节点
    struct listNode *prev;

    // 后置节点
    struct listNode *next;

    // 节点的值
    void *value;

} listNode;

上面的node定义就是链表的每个元素了。在Redis中,同时定义了一个双向链表,并给这个链表定义了三个操作函数(方法):

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;

同时,也定义了一个双向链表的迭代器

typedef struct listIter {

    // 当前迭代到的节点
    listNode *next;

    // 迭代的方向,有从头到尾,也有从尾到头的方向
    int direction;

} listIter;

看到了上面的双向节点,我们可能会问在Redis中,他是采用有环的链表呢还是无环链表?我们以后的功能来看,因为要实现有环的链表只需要使用list的head的prev不为NULL、tail的next不为NULL即可实现。

在adlist.h中,同时定义了一堆宏,用来实现简单的函数功能

// 返回给定链表所包含的节点数量
#define listLength(l) ((l)->len)
// 返回给定链表的表头节点
#define listFirst(l) ((l)->head)
// 返回给定链表的表尾节点
#define listLast(l) ((l)->tail)
// 返回给定节点的前置节点
#define listPrevNode(n) ((n)->prev)
// 返回给定节点的后置节点
#define listNextNode(n) ((n)->next)
// 返回给定节点的值
#define listNodeValue(n) ((n)->value)

// 将链表 l 的值复制函数设置为 m
#define listSetDupMethod(l,m) ((l)->dup = (m))
// 将链表 l 的值释放函数设置为 m
#define listSetFreeMethod(l,m) ((l)->free = (m))
// 将链表的对比函数设置为 m
#define listSetMatchMethod(l,m) ((l)->match = (m))

// 返回给定链表的值复制函数
#define listGetDupMethod(l) ((l)->dup)
// 返回给定链表的值释放函数
#define listGetFree(l) ((l)->free)
// 返回给定链表的值对比函数
#define listGetMatchMethod(l) ((l)->match)

上面定义了基本的获取一个链表的常规属性的函数(宏),根据我们对一个数据结构的常用操作方式,下面就应该是定义创建、初始化、增加、删除等的方法(函数)了。

 
//创建一个新列表
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;
}
//释放整个链表以及链表里面的节点
void listRelease(list *list)
{
    unsigned long len;
    listNode *current, *next;

    // 指向头指针,我对于这里的写法,总会觉得有问题,加入list==NULL呢?
    current = list->head;
    // 遍历整个链表
    len = list->len;
    while(len--) {
        next = current->next;

        // 如果有设置值释放函数,那么调用它
        if (list->free) list->free(current->value);

        // 释放节点结构
        zfree(current);

        current = next;
    }

    // 释放链表结构
    zfree(list);
}
//用一个指定的value值创建一个node加入到list的头部
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来创建node,并加到list的末尾
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;
}
//用指定的value来创建一个node,并根据传入的after参数来判断是要添加到指定的节点之前还是之后
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;
}
//从list中删除指定的node
void listDelNode(list *list, listNode *node)
{
    // 调整前置节点的指针,我在用java来编写程序的时候,特别害怕这种写法,因为遇到空指针的时候,这里一定会报错,所以,下面的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创建迭代器
listIter *listGetIterator(list *list, int direction)
{
    // 为迭代器分配内存
    listIter *iter;
    if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;

    // 根据迭代方向,设置迭代器的起始节点
    if (direction == AL_START_HEAD)//这里的AL_START_HEAD==0
        iter->next = list->head;
    else
        iter->next = list->tail;

    // 记录迭代方向
    iter->direction = direction;

    return iter;
}
//获得迭代器当前的节点,因为返回的都是指针,所以,是可以改变的
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;
}
//释放迭代器
void listReleaseIterator(listIter *iter) {
    zfree(iter);
}
//复制整个list,需要注意的是,如果原始的list的dup属性为空,那么,前后两个list会共享节点的指针,因此,要特别小心
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;
}
//在列表中寻找第一次出现指定key的节点,需要注意的是,如果在list中的节点的match函数为空,那么就直接比较value的指针,找到了第一个就返回,找不到就返回为NULL
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中指定索引的节点,节点可以为正数,也可以是负数,当为负数的时候,-1代表list的tail,依次类推,如果不为负数,那么,索引就是从0开始的
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;
}

接下来的两个函数,根据名字就可以看出来,倒回,什么意思呢?就是讲list的迭代器的

//设置为head->tail
void listRewind(list *list, listIter *li) {
    li->next = list->head;
    li->direction = AL_START_HEAD;
}
//设置为tail->head
void listRewindTail(list *list, listIter *li) {
    li->next = list->tail;
    li->direction = AL_START_TAIL;
}

接下来的这个函数,总觉得名字和他实现的内容不匹配,这个函数要实现的是将链表的tail挪成list的head。

void listRotate(list *list) {
    listNode *tail = list->tail;

    if (listLength(list) <= 1) return;

    /* Detach current tail */
    // 取出表尾节点
    list->tail = tail->prev;
    list->tail->next = NULL;

    /* Move it as head */
    // 插入到表头
    list->head->prev = tail;
    tail->prev = NULL;
    tail->next = list->head;
    list->head = tail;
}

好了,Redis的List基本就是这样子啦

时间: 2024-10-10 15:09:20

Redis研究-3.1 数据结构之链表的相关文章

Redis研究-3.3数据结构之树与查找、排序等(后续)

3.树.二叉树.森林之间的转换 前面我们又说到,二叉树中的节点我们可以表示成一个具有左孩子域.右孩子域.双亲域.自身数据域的一个数据结构,那么对于一般的树或者森林中的节点来说,能不能也这样子表示呢?答案是可以的,表示成二叉树节点的形式,我们就能很好的使用二叉树的一些特性和算法. 在二叉树中,left表示节点的左孩子.right表示节点的右孩子,那么,对于一般的树节点来看,如果存在孩子,第一个孩子就是对应的left区域,如果有第二个.第三个孩子等,就用right形成一个链表,那么,这种树就转换为二

Redis研究-3.2 数据结构之关联数组(字典)

这个章节要学习到的源码都是在dict.h和dict.c两个文件中 在java语言或者其他支持关联数组的的语言中,我们最先知道的就是关联数组(字典)就是key-value的"数组",那么,在Redis中又是如何一步一步来实现的呢?我们先分解一下,关联数组(字典)就是key-value的"数组",这句话,首先必须要有key-value这个结构 //key-value结构 typedef struct dictEntry { // 键 void *key; // 值 un

Redis研究-3.3数据结构之树与查找、排序等

1.树相关的内容 1.1 Tree概念 树是n(n>=0)个节点的有限集.n=0的时候,我们把它叫做空树.在任何一非空树中满足两个特点:(1) 有且只有一个叫做根的节点.(2)n>1时,其余节点可分为m(m>0)个 互不相交的有限集T1,T2,...其中每一个结合本身也是一棵树. 上面的这概念用到了递归的定义. 树的相关概念: 节点的度:是指这个节点的子树的个数. 树的度:是指树的节点中拥有最大多数量的节点的度. 节点的关系:节点的子树的根叫做该节点的 孩子,相应的,该节点成为孩子的 双

Redis 中 5 种数据结构的使用场景介绍

这篇文章主要介绍了Redis中5种数据结构的使用场景介绍,本文对Redis中的5种数据类型String.Hash.List.Set.Sorted Set做了讲解,需要的朋友可以参考下 一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String——字符串 Hash——字典

redis中5种数据结构的使用

一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String——字符串Hash——字典List——列表Set——集合Sorted Set——有序集合 下面我们就来简单说明一下它们各自的使用场景: 1. String——字符串 String 数据结构是简单的 key-valu

Redis中5种数据结构的使用场景

一.redis 数据结构使用场景 原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构,那每种数据结构的使用场景都是什么呢? String--字符串Hash--字典List--列表Set--集合Sorted Set--有序集合 下面我们就来简单说明一下它们各自的使用场景: 1. String--字符串 String 数据结构是简单的 key-valu

基本数据结构:链表(list)

copy from:http://www.cppblog.com/cxiaojia/archive/2012/07/31/185760.html 基本数据结构:链表(list) 谈到链表之前,先说一下线性表.线性表是最基本.最简单.也是最常用的一种数据结构.线性表中数据元素之间的关系是一对一的关系,即除了第一个和最后一个数据元素之外,其它数据元素都是首尾相接的.线性表有两种存储方式,一种是顺序存储结构,另一种是链式存储结构. 顺序存储结构就是两个相邻的元素在内存中也是相邻的.这种存储方式的优点是

数据结构之链表单向操作总结

链表是数据结构的基础内容之一,下面就链表操作中的创建链表.打印链表.求取链表长度.判断链表是否为空.查找结点.插入结点.删除结点.逆转链表.连接链表.链表结点排序等进行总结. 1.创建表示结点的类,因为链表操作中需要比较结点,因此结点需要实现comparable接口. public class Node implements Comparable<Node> { private Object data; private Node next; //构造函数 public Node() { thi

数据结构-复杂链表的复杂

题目:请实现函数ComplexListNode*  Clone(ComplexListNode* pHead),复杂一个复杂链表.在复杂链表中,每个节点除了有一个Next指针指向下一个节点外,还有一个Sibling指向链表中的任意节点或者NULL. 分析:第一反应是先复制Next,再复制Sibling.但是这种方式需要两次遍历.时间性不是很好.所以利用一个长链表方式解决时间效率. /* 剑指offer面试题26 */ #include <iostream> #include <cstri