redis底层实现逻辑,如上一篇博客文章中开头就有大量阐述,地址:https://blog.csdn.net/qq_39291929/article/details/103409000
接下来描述一下
redis五种数据结构
1、简单动态字符串(String)SDS
Redis 是用 C 语言写的,但是对于Redis的字符串,却不是 C 语言中的字符串(即以空字符’\0’结尾的字符数组),它是自己构建了一种名为 简单动态字符串(simple dynamic string,SDS)的抽象类型,并将 SDS 作为 Redis的默认字符串表示
结构:
1、len 保存了SDS保存字符串的长度
2、buf[] 数组用来保存字符串的每个元素
3、free j记录了 buf 数组中未使用的字节数量
用SDS保存字符串 “Redis”具体图示如下:
空间预分配:
若修改之后sds长度小于1MB,则多分配现有len长度的空间
若修改之后sds长度大于等于1MB,则扩充除了满足修改之后的长度外,额外多1MB空间
惰性空间释放:实际长度从长变短时并不释放空间,避免缩短短字符串时所需的内存重分配操作,并未将来可能的增长操作提供了优化。
c语言字符串和sds字符串区别
2、链表----无环双向链表
链表三个主要参数:
head指向具体双向链表的头
tail指向具体双向链表的尾
len双向链表的长度
链表节点:带有prev和next指针
如图所示:
如图2,详细描述链表,单链表只有next,双链表既有next还有prev指向上一个节点
Redis链表特性:
①、双端:链表具有前置节点和后置节点的引用,获取这两个节点时间复杂度都为O(1)。
②、无环:表头节点的 prev 指针和表尾节点的 next 指针都指向 NULL,对链表的访问都是以 NULL 结束。
③、带链表长度计数器:通过 len 属性获取链表长度的时间复杂度为 O(1)。
3、压缩列表(ziplist)---redis 3.0后改为quicklist
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者一个整数值
理论常识:
数组列表,如图所示:
压缩列表,明显节省了空间
redis 3.0之后list键已经不直接用ziplist和linkedlist作为底层实现了,取而代之的是quicklist
quicklist实际上是ziplist和linkedList的混合体,它将linkedList按断切分,每一段使用zipList来紧凑存储,多个zipList之间使用双向指针串接起来
quicklist结构,如图所示,
结构上继承了双向链表的特性,比如
- prev: 指向链表前一个节点的指针。
- next: 指向链表后一个节点的指针。
- zl: 数据指针。如果当前节点的数据没有压缩,那么它指向一个ziplist结构;否则,它指向一个quicklistLZF结构。
- sz: 表示zl指向的ziplist的总大小(包括
zlbytes
,zltail
,zllen
,zlend
和各个数据项)。需要注意的是:如果ziplist被压缩了,那么这个sz的值仍然是压缩前的ziplist大小。 - count: 表示ziplist里面包含的数据项个数。这个字段只有16bit。稍后我们会一起计算一下这16bit是否够用。
- encoding: 表示ziplist是否压缩了(以及用了哪个压缩算法)。目前只有两种取值:2表示被压缩了(而且用的是LZF压缩算法),1表示没有压缩
4、跳表
我们都知道链表中查找某个元素只能挨个遍历链表中的节点进行匹配,所以链表的查询节点的效率是很慢的,所以就有针对于链表查找优化的数据结构平衡二叉树和 跳表这样的数据结构。
在Redis中,sortedSet使用了跳表的结构来保存数据,跳表的基本逻辑是在链表的基础上进行排序,然后在进行二分查找法的优化,根据规则抽取链表中的节点构建成另外一个链表,查找元素时通过最上层的链表逐渐向下查找,最终找到匹配元素或者返回空结果。
跳表是基于一条有序单链表构造的,通过构建索引提高查找效率,空间换时间,查找方式是从最上面的链表层层往下查找,最后在最底层的链表找到对应的节点:
如图所示:
跳表有以下几点结构说明:
header: 跳表表头
tail:跳表表尾
level:层数最大的那个节点的层数
length:跳表的长度实现部分有以下几点说明:
表头:是链表的哨兵节点,不记录主体数据。是个双向链表分值是有顺序的o1、o2、o3是节点所保存的成员,是一个指针,可以指向一个SDS值。层级高度最高是32。没每次创建一个新的节点的时候,程序都会随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是“高度”redis五种数据结构的实现
跳跃表 level 层级完全是随机的。一般来说,层级越多,访问节点的速度越快。
5、哈希表
动态字符串和hash区别,如图所示:
哈希表是一个结构体类型,包含四个成员属性:
- table 是一个数组,数组的每个元素都是一个指向 dict.h/dictEntry 结构的指针(键值对);
- size 记录哈希表的大小,即 table 数组的大小,且一定是2的幂;
- used 记录哈希表中已有结点的数量;
- sizemask 用于对哈希过的键进行映射,索引到 table 的下标中,且值永远等于 size-1。具体映射方法很简单,就是对 哈希值 和 sizemask 进行位与操作,由于 size 一定是2的幂,所以 sizemask=size-1,自然它的二进制表示的每一个位(bit)都是1,等同于取模;
由上图我们已经知道,哈希表的定义中包含一个table数组,它的每一个元素都是一个指向dictEntry结构类型的指针,其实这里的dictEntry便是哈希表的节点。
由上图可知每一个dictEntry结构都是一个健值对,且有一个next指针,来维持节点之间的链表形态。下面我们详细看下每个字段的具体含义:
- key 是键值对中的键;
- v 是键值对中的值,它是一个联合类型,方便存储各种结构;
- next 是链表指针,指向下一个哈希表节点,他将多个哈希值相同的键值对串联在一起,用于解决键冲突;
可参考地址;Redis哈希表的设计与实现 - 知乎
redis中的hash表采用的是渐进式hash的方式:
1、redis字典(hash表)底层有两个数组,还有一个rehashidx用来控制rehash
2、初始默认hash长度为4,当元素个数与hash表长度一致时,就发生扩容,hash长度变为原来的二倍
3、redis中的hash则是执行的单步rehash的过程:
4、每次的增删改查,rehashidx+1,然后执行对应原hash表rehashidx索引位置的rehash
6、整数
整数集合(intset)是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为int16_t、int32_t或者int64_t的整数值,并且保证集合中不会出现重复元素。
我们知道,intset可能会随着数据的添加而改变它的数据编码:最开始,新创建的intset使用占内存最小的INTSET_ENC_INT16(值为2)作为数据编码。每添加一个新元素,则根据元素大小决定是否对数据编码进行升级。
升级的好处:
1.提升整数集合的灵活性;
3.尽可能节约内存。
降级
整数集合不支持降级操作,一旦对数组进行了升级,编码就会一直保持升级后的状态