Redis系列——底层数据结构分析

本文深入探讨了Redis的内部数据结构,包括简单动态字符串SDS、链表、字典(详细介绍了rehash过程)、跳表以及整数集合和压缩列表。SDS优化了C语言字符串的管理,链表是双端的,字典使用哈希表实现并进行了rehash的详解,跳表用于有序集合,提供了高效的查找效率。整数集合适用于少量元素的集合,压缩列表则用于节省内存。
摘要由CSDN通过智能技术生成

前言

redis作为k->v的数据库,广泛的使用在缓存中。我每次用redis时就直接调用api或者spring中封装好的对象,从来没有深入研究过内部结构。这也导致有时候面试被问到了,或者自己在调用的时候一知半解,出了问题不知道怎么去排查。
最近在看好多redis的技术书籍以及相关博客,也同时写一系列的redis文章,便于自己梳理脉络,也方便新手入门。

由于本人技术水平限制,错误在所难免,若有发现请及时留言,谢谢。

简单动态字符串SDS

redis用c语言编写。在c语言中字符串用以\0结尾的数组。但是redis新建了一种数据结构作为默认的字符串。
比如 set msg abc,建立了内容为msg的SDS作为键,内容为abc的SDS作为值的键值对。
在这里插入图片描述

SDS结构体
在这里插入图片描述

比起c语言中的字符串数组,多了用于管理的单元

结尾的\0不算在len里面,这样可以重用C语言中字符串的库

C语言中的字符串数组,在增长和缩短时需要对底层数组内存重新分配。由于redis是作为数据库使用的,那么就会频繁的增长和缩短字符串长度。

redis采用SDS结构做出了优化

1、空间预分配(优化增长)
在字符串增长时会为SDS分配额外的未使用空间。分配与len相同的最多1M额外空间。
2、惰性空间释放(优化缩短)
缩短字符串时,删除数组中的字符,但是不会释放空间,算成额外空间,增加free的值。

由于数据库可能保存二进制的数据,包括视频、音频等。所以如果\0作为结尾标志,那么会丢失掉后面的有效数据。SDS用len值判断数据有效范围。

链表

redis自己定义了一个双端链表

typedef struct listNode {
   
	// 指向前一个节点
	struct listNode *prev;
	// 指向后一个节点
	struct listNode *next;
	// 当前节点的值
	void *value;
}listNode

这个与通常的链表节点一致,不做解释。
在这里插入图片描述基于上述的链表节点,redis定义了自己的链表

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

在这里插入图片描述

可以看到表头节点的pre指向NULL,表尾节点的next指向NULL

字典

这一小节会比较繁琐,并且以上面两种数据结构为基础,要有耐心
说到字典,背过java八股文的朋友应该很熟悉hashmap,嘿嘿!
但是很少人了解过redis内部的map,毕竟面试问的很少嘛

一个键和一个值进行关联,关联的键和值成为键值对
redis数据库将字典作为底层的数据结构,对数据库crud就是对字典操作
比如 SET msg abc,在数据库中建立了一个键为msg,值为abc的键值对,键值对就是保存在字典中。
哈希键包含的键值对较多时,redis也使用字典作为底层实现
比如web包含许多哈希键
在这里插入图片描述

可以看到键为web,值为一个字典,字典包含许多键值对
键值对键baidu,值为baidu.com
键值对键google,值为google.com

那么这个具体是怎么实现的呢?
我们自顶向下的拆解这个复杂的数据结构

typedef struct dict {
   

    // 类型特定函数
    dictType *type;

    // 私有数据
    void *privdata;

    // 哈希表
    dictht ht[2];

    // rehash 索引
    // 当 rehash 不在进行时,值为 -1
    int rehashidx; /* rehashing not in progress if rehashidx == -1 */

    // 目前正在运行的安全迭代器的数量
    int iterators; /* number of iterators currently running */

} dict;

dictType保存了许多用于操作键值对的函数,privatedata保存用于dictType的参数。
iterators 目前正在运行的安全迭代器的数量,我们放在后面讲解这个。
暂时忽略这几个参数的具体实现,我们重点讲存储的数据结构。
ht是保存了两个哈希表的数组,一般情况只使用ht[0],ht[1]在对ht[0]进行rehash操作时用。用trehashidx标志现在是否正在进行rehash。
那么我们在脑海中可以想象出来这样的结构
在这里插入图片描述

现在关键就在于如何这个用来保存键值对的结构是什么样子的。

typedef struct dictht {
   
    
    // 哈希表数组
    dictEntry **table;

    // 哈希表大小
    unsigned long size;
    
    // 哈希表大小掩码,用于计算索引值
    // 总是等于 size - 1
    unsigned long sizemask;

    // 该哈希表已有节点的数量
    unsigned long used;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值