Redis数据结构与对象(一)

本文深入探讨Redis中关键数据结构,如SDS、链表、字典、跳跃表的内部机制与特性,揭示其在高性能数据库中的应用优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Redis数据结构与对象

字符串

Redis,自己构建了一种名为简单动态字符串的抽象类型SDS

数据结构

struct sdshdr{
    // 记录buf数组中已经使用字节的数量
    // 等于SDS所保存字符串的长度
    int len;
    // 记录buf数组中未使用的字节的数量
    int free;
    // 字节数组,用于保存字符串
    char buf[];
}

比较C的字符串,SDS的优点:

  • 常熟复杂度获取字符串长度
  • 杜绝缓冲区溢出
  • 减少修改字符串长度时所需的内存重分配次数
  • 二进制安全
  • 兼容C的字符串函数

链表

链表提供了高效的节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度

使用:链表键、发布与订阅、慢查询、监视器等

数据结构:

type struct listNode{
    Struct listNode *prev;
    Struct listNode *next;
    void *value;
}listNode;

typedef struct list{
    listNode *head;
    listNode *tail;
    unsigned long len;
    /* 节点值复制函数 */
    void *(*dup)(void *ptr);
    /* 节点值释放函数 */
    void (*free)(void *ptr);
    /* 节点值对比函数 */
    int (*match)(void *ptr,void *key);
}list;

特性:
1、双端
2、无环:head->prev = null tail->next = null
3、带表头和表尾指针
4、带链表长度计数器
5、多态 void* 保存节点值

字典

字典(符号表、关联数组、映射map),是一种用于保存键值对(key-value)的抽象数据结构

使用:redis的数据库底层实现就是字典,对数据库的增删改查就是建立在字典之上

数据结构:

哈希表定义
typedef struct dictht{
    // 哈希数组
    dictEntry **table;
    // 哈希表大小
    unsigned long size;
    //哈希大小掩码,用于计算索引值,总是等于size-1
    unsigned long sizemask;
    //该哈希表已有节点的数量
    unsigned long used;
}dictht;

其中table是一个数组,保存着dictEntry的键值对,其实也很好理解,想一想java的hashmap的实现

哈希表节点定义
typedef struct dictEntry{
    // 键
    void *key
    // 值
    union{
        void *val;
        uint64_t u64;
        int64_t s64;
    }
    struct dictEntry *next;
}dictEntry;

union表示键值对的值可以多样化(指针、整数)
next指针也就表示了用链表解决哈希冲突

字典的定义
typedef struct dict{
    // 类型特定函数
    dictType *type;
    // 私有数据
    void *private;
    // 哈希表
    dictht ht[2];
    // rehash索引
    // 当rehash不在进行时,值为-1
    int trehashidx;  
}dict;

type指向了dictType结构的指针,包括以下几个函数:

  • hashFunction 计算哈希值
  • keyDup 复制键
  • valDup 复制值
  • keyCompare 对比键
  • keyDestructor 销毁键
  • valDestructor 销毁值

其中ht数组表示哈希表,字典只使用ht[0]哈希表,h[1]只会在rehash时使用
trehashidx记录了rehash目前的进度

如何计算哈希值?
hash = dict->type->hashFunction(key)
Redis使用的MurmurHash2算法来计算hash值

如何解决hash冲突?
链地址法,头插入,因为dictEntry没有指向尾结点的指针 ??? 那key相同怎么办

如何做rehash的?
渐进式rehash,不是一次性、集中式的完成的。考虑到如果哈希表里有大量键值对,采用分多次、渐进式地完成的。
在渐进式rehash期间,字典的删除、查找、更新等操作会在两个哈希表上进行,比如查找某key,会在ht[0]和ht[1]都找

跳跃表

跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找
Redis使用跳跃表:1、实现有序集合 2、在集群节点中用作内部数据结构

跳跃表节点
typedef struct zskiplistNode {
    // 后退指针
    struct zskiplistNode *backward;
    // 分值
    double score;
    // 成员对象
    robj *obj;
    // 层
    struct zkiplistLevel {
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
}zskiplistNode;

1、层
增加层能加快访问其他节点的速度
2、前进指针
指向表尾方向的前进指针
3、跨度
用来记录两个节点之间的距离
4、后退指针
表尾向表头方向访问节点
5、分值和成员
节点的分值(score属性)是一个double类型的浮点数,跳跃表中所有节点都按照分值从小到大排序
节点的成员(obj属性)是一个指针,它指向一个字符串对象,而字符串对象则保存着一个SDS值

跳跃表
通过一个zskiplist跳跃表结构来持有这些节点
typedef struct zskiplist {
    // 表头节点和表尾节点
    structz skiplistNode *header, *tail;
    // 表中节点的数量
    unsigned long length;
    // 表中层数最大的节点的层数
    int level;
}zskiplist;
  • header、tail分别指向跳跃表的表头和表尾
  • 跳跃表的层高都是1-32之间的随机数
  • 在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的
  • 跳跃表中的节点按照分值大小进行排序,分值相同时,按照成员对象的大小排序
  • 插入快,无锁实现,不需要旋转保持平衡(对比红黑树)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值