Redis 底层数据结构(sds,链表,字典,跳跃表,整数集合,压缩列表)

Redis使用多种数据结构实现高效存储和操作,如动态字符串SDS提供O(1)长度获取,链表支持双端操作,字典基于哈希表解决键冲突,跳跃表提供快速查找,整数集合优化内存,压缩列表节省空间。这些数据结构使得Redis在性能和内存使用上达到平衡。
摘要由CSDN通过智能技术生成

Redis 底层数据结构

1.动态字符串SDS
2.链表
3.字典
4.跳跃表
5.整数集合
6.压缩列表

动态字符串

源码
sds是Redis中最基本的数据结构,使用一整段连续的内存来存储sds头信息和数据信息。其中,字符串的header包括了sds的字符串长度,字符串的最大容量以及sds的类型这三种信息。这三种基本的类型能够简化许多sds的操作,如字符串的长度只需要O(1)即可,而strlen的O(N)好很多。另外,sds还提供了很多的操作函数,在其拥有的字符串的原生特性之外,还能动态扩展内存和符合二进制安全等。

链表

源码
采用双端链表实现

typedef struct listNode {
    //前节点
    struct listNode *prev;
    //后节点
    struct listNode *next;
    //节点值
    void *value;
} listNode;


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;

在这里插入图片描述
在这里插入图片描述

Redis 的链表实现的特性可以总结:
1.双端:链表节点带有prev和next指针,获取某个节点的前置节点和后置节点的复杂度都是O(1)。
2.无环:表头节点的prev指针和表尾节点的 next指针都指向NULL,对链表的访问以NULL为终点。
3、带表头指针和表尾指针:通过list结构的head指针和tail指针,程序获取链表的表头节点和表尾节点的复杂度为O(1)。
4.带链表长度计数器:程序使用list结构的len属性来对list持有的链表节点进行计数,程序获取链表中节点数量的复杂度为O(1).
5.多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup,free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。

字典

源码

字典底层由哈希表构成,每个哈希表有多个哈希表节点,每个哈希节点保存一个键值对
类似java的HashMap

typedef struct dictht {
    //哈希数组
    dictEntry **table;
    //哈希表大小
    unsigned long size;
    //size-1  和哈希值决定放到table哪个位置
    unsigned long sizemask;
    //哈希表已有节点数量
    unsigned long used;
} dictht;
typedef struct dictEntry {
	//键
    void *key;
    //值
    union {
        void *val;
        uint64_t u64;
        int64_t s64;
        double d;
    } v;
    //指向哈希表节点形成链表
    struct dictEntry *next;
} dictEntry;
typedef struct dict {
    //类型
    dictType *type;
    //私有数据
    void *privdata;
    //哈希表
    dictht ht[2];
    //rehash 索引
    long rehashidx; /* rehashing not in progress if rehashidx == -1 */
    int16_t pauserehash; /* If >0 rehashing is paused (<0 indicates coding error) */
} dict;

在这里插入图片描述

添加一个元素

当要将一个新的键值对添加到字典里面时,程序需要先根据键值对的键计算出哈希值和索引值,然后再根据索引值,将包含新键值对的哈希表节点放到哈希表数组的指定索引上面。

哈希冲突

当有两个或以上数量的键被分配到了哈希表数组的同一个索引上面时,我们称这些键发生了冲突( collision )。
Redis的哈希表使用链地址法 ( separate chaining)来解决键冲突,每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,这就解决了键冲突的问题。

rehash

随着操作的不断执行,哈希表保存的键值对会逐渐地增多或者减少,为了让哈希表的负载因子( load factor)维持在一个合理的范围之内,当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩。
扩展和收缩哈希表的工作可以通过执行rehash(重新散列)操作来完成,Redis对字典的哈希表执行rehash的步骤如下:
1)为字典的ht [ 1]哈希表分配空间,这个哈希表的空间大小取决于要执行的操作,以及ht[0]当前包含的键值对数量(也即是ht [ 0 ].used属性的值):
·如果执行的是扩展操作,那么ht[1]的大小为第一个大于等于ht [ 0 ] .used*2的2"( 2的n次方幂);
·如果执行的是收缩操作,那么ht[1]的大小为第一个大于等于ht[0] . used的2"。
2)将保存在ht [0]中的所有键值对rehash到ht[1]上面: rehash 指的是重新计算键的哈希值和索引值,然后将键值对放置到ht[1]哈希表的指定位置上。
3)当ht [0]包含的所有键值对都迁移到了ht[1]之后( ht [0]变为空表),释放ht [0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下一次rehash做准备。

跳跃表

源码
跳跃表支持平均O(logN),最坏O(N)复杂度查找 ,有序集合的底层实现

/* ZSETs use a specialized version of Skiplists */
typedef struct zskiplistNode {
	//数据
    sds ele;
    //节点分值
    double score;
    //节点的后退指针
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        //节点的前进指针
        struct zskiplistNode *forward;
        //跨度
        unsigned long span;
    } level[];
} zskiplistNode;
typedef struct zskiplist {
	//跳跃表的头结点和尾节点
    struct zskiplistNode *header, *tail;
    //跳跃表的长度
    unsigned long length;
    //跳跃表的层数
    int level;
} zskiplist;

在这里插入图片描述
在这里插入图片描述

图中可以看出 zskiplist 结构, header, tail 节点(zskiplistNode 节点) 结构,bw(forward)代替后退指针 ,图中level 代表最大层数是5(最后一个节点),length为3 代表有3个 zskiplistNode 节点 ,每个zskiplistNode 节点 的分值(score)按从小到大排序,zskiplistLevel 里面包含指向其他元素的指针,这些层快速访问其他元素,一般来说层数越多访问其他元素就越快,每次创建跳跃表的时候都会随机生产一个1-32长度的level数组 这个大小可以看做这个层的“”高度“”,zskiplistLevel .span 用于记录(zskiplistNode )节点中zskiplistLevel 到 其他(zskiplistNode )节点中zskiplistLevel 的距离比如第一个节的zskiplistLevel 到第三个节点的zskiplistLevel 距离是2,header节点的第四个zskiplistLevel 到第一个节点的zskiplistLevel 距离为1,如果距离为0代表没有连接任何节点比如第三个节点(zskiplistNode ),在跳跃表中每个节点保存的成员对象必须是唯一,但每个节点的分值可以相同,分值一样的节点会按照成员对象在字典中的大小, 从小到大排序。

整数集合

源码

typedef struct intset {
	//类型
    uint32_t encoding;
    //长度
    uint32_t length;
    //数据
    int8_t contents[];
} intset;

在这里插入图片描述
图中 整数集的结构 类型为 intset_enc_int16 ,长度5(数组contents长度) ,contents 数组结构存储数据

升级过程

1.更新添加元素的类型扩展数组大小,并且为新元素分配空间,
2.把所有旧元素的类型转化成和新元素一样,放到正确的位置上面,需要保持底层数组有序性
3.把新元素添加到数组

整数集合小结

1.是集合键的底层实现
2.底层实现为有序,无重复数组结构,会根据添加元素的类型而改变
3.升级操作会尽可能的减少内存
4.只支持升级,不支持降级

压缩列表

源码

压缩列表的整体结构如下(用redis源码注释):


 * <zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>

zlbytes代表ziplist总字节数,包括zlbytes
zltail 代表最后一个entry的偏移量
zllen 代表 entry数量
zlend代表ziplist固定结尾,值固定为0xFF(十进制255)
entry代表ziplist的各节点,具体结构不定
entry结构

**  <prevlen> <encoding> <entry-data>

prevlen表示的是前一个entry的长度,用于反向遍历,即从最后一个元素遍历到第一个元素。因每个entry的长度是不确定的,所以要记录一下前一个entry的长度。prevlen本身的长度也是不定的,与前一entry的实际长度有关。若长度小于254,只需要1B就可以了。若实际长度大于等于254,则需要5B
可以看看这个文章

连锁更新问题

在这里插入图片描述
在这里插入图片描述
增加删除节点会导致重新计算prevlen 的值 需要重新分配内存大小 ,若长度小于254,只需要1B就可以了。若实际长度大于等于254,则需要5B。最坏复杂度为O(N)及所有节点更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值