20200829——Redis 数据结构底层

简单动态字符串

redis 没有直接使用c语言传统的的字符串表示,而是自己构建了一种名为简单动态字符串的可以修改的抽象类型,并将sds作为redis的默认字符串表示

SDS的结构定义

在这里插入图片描述
free属性值是0,表示这个sds没有分配任何未使用空间
len属性是5,表示这个sds保存了一个5字节长的字符串
buf属性表示是一个char类型的数组,数组最后保存了空字符’\0’

sds与c字符串的区别

sds获取字符串长度复杂度为常数

sds通过获取len属性就可以得到字符串的长度,时间复杂度为O(1)
c字符串需要遍历字符串,时间复杂度为O(N)

sds杜绝了缓冲区溢出

c字符串如果没有重新分配空间,直接修改字符串的话,可能会造成数据溢出。

当sds中的api需要对sds进行修改时,会先检查sds空间是否满足修改所需要的值,如果不满足,则自动将sdsk空间扩展至所需大小。

减少内存重分配次数

sds通过空间预分配和惰性空间释放两种优化策略来减少内存重分配次数。

空间预分配

redis通过额外分配未使用的空间,优化了sds的字符串增长操作,减少了连续执行字符串增长操作所需的内存分配次数。

惰性空间释放

惰性空间释放用于优化sds的字符串缩短操作,当sds缩短时,程序并不会立即回收缩短后多出来的空间,而是使用free属性,将这些字节的数量记录起来,等待将来使用。

二进制安全

sds api会处理二进制的方式来处理sds存放在buf数组里的数据,程序不会对其中的数据做任何的限制,过滤或者假设,所以sds的api都是二进制安全的。

sds兼容部分c字符串函数

SDS之所以在末尾保存一个空字符’\0’,是为了使用一些c字符串<string.h>函数库,避免不必要的代码重复。
例如 字符串对比函数:<string.h>/strcasecmp函数

双向链表

双向链表结构

在这里插入图片描述

链表节点结构

在这里插入图片描述

redis的链表实现的特性

双端
无环
带表头指针和表尾指针
带链表长度计数器
多态

字典

字典,又被称为符号表,关联数组或者映射,是用于保存键值对的抽象数据结构。
字典中的每个键,都是独一无二的,程序可以在字典中根据键查找与之相关的值,或者通过键来更新值,又或者根据键来删除整个键值对,

字典的实现结构

redis的字典使用哈希表作为底层实现,一个哈希表里面可以有多个哈希表节点,而每个哈希表节点就保存了字典中的一个键值对。

哈希表节点

在这里插入图片描述

key属性保存着键值对中的键;
v属性保存着键值对中的值,其中值用union定义,支持三种数据类型。
next属性是指向另一个哈希表节点的指针,这个指针可以将多个哈希值相同的键值对连接在一起,以此来解决键冲突的问题。

哈希表

在这里插入图片描述
table属性是一个数组,数组中的每个元素都是一个指向哈希表节点的指针,每个节点都保存着一个键值对;
size属性记录了哈希表的大小,也就是table数组的大小;
sizemask属性的值总是等于size-1,这个属性和哈希值一起决定一个键应该被放到table数组的那个索引上面;
used属性记录了哈希表目前已有节点的数量。

字典

在这里插入图片描述
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数;
privdata属性保存了需要传给那些类型特定函数的可选参数;
ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,ht[1]只有在对ht[0]哈希表进行rehash操作时使用;
trehashidx属性是rehash索引,没有进行rehash操作时值都为-1.

在这里插入图片描述

跳跃表

跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群结点中用作内部数据结构,除此之外,跳跃表没有其他用途。

跳跃表的节点的结构

在这里插入图片描述
层:跳跃表节点的 level[] 数组可以包含多个元素,每个元素都包含一个指向其他节点的指针 和 跨度,下标从0开始为第一层;
前进指针:每个层都有一个指向表尾方向的前进指针,用于从表头向表尾方向访问节点;
跨度:层的跨度用于记录两个节点之间的距离,指向NULL的所有前进指针的跨度为0;跨度用来计算节点的排位:在查找某个节点的过程中,将沿途访问过的所有层的跨度累计起来,得到的结果就是目标节点在跳跃表中的排位。
后退指针:后退指针用于从表尾向表头方向访问节点,每次只能后退一个节点;
分值和成员:
分值:一个double类型的浮点数,所有节点都按照分值从小到大排序,多个节点可以包含相同的分值;
成员:一个指针,指向一个字符串对象,该字符串对象保存着一个SDS值,成员对象必须是唯一的。

跳跃表的结构

仅靠过个跳跃表节点就可以组成一个跳跃表,但通过使用一个zskiplist结构来持有这些节点,就可以很方便地对整个跳跃表进行处理。
zskiplist结构如图:在这里插入图片描述
header和tail指针分别指向跳跃表的表头和表尾节点;
length属性记录节点的数量;
level属性记录层数最高的几点的层数量;

在这里插入图片描述

整数集合

整数集合是Redis用于保存整数值的集合抽象数据结构,它可以保存类型为 int16_t 、int32_t 或者 int64_t 的整数值,并且保证集合中不出现重复值。

整数集合的结构

在这里插入图片描述
encoding:contents数组中元素的类型,有 INTSET_ENC_INT16、INTSET_ENC_INT32 和 INTSET_ENC_INT64 三种,分别表示contents数组中元素类型为 int16_t(16位二进制)、int32_t 和 int64_t 类型。
contents:整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值的大小从小到大有序地排列,数组中不包含重复项。
length:记录了整数集合包含的元素数量。

在这里插入图片描述

整数集合的升级

如果我们想要添加一个新元素到整数集合里面,但是新元素的类型比整数集合原有的元素类型都要长时,我们就要对整数集合进行升级,然后才能将新元素添加到整数集合里面。
另外,还需注意,Redis的整数集合不支持降级。

升级整数集合并添加新元素共分为三步进行:

1)根据新元素的类型,扩展整数集合底层数组的空间大小,并为新元素分配空间。
2)将底层数组现有的所有元素都转换成与新元素相同的类型,并将类型转换后的元素继续维持底层数组的有序性质不变。
3)将新元素添加到底层数组里面。

在这里插入图片描述

压缩列表

压缩列表是列表键和哈希键的底层实现之一。当一个列表键值包含少量列表键,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。

压缩列表的构成

压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点 (entry),每个节点可以保存一个字节数组或者一个整数值。在这里插入图片描述

zlbytes属性:表示压缩列表的总字节长度;
zltail属性:记录压缩列表表尾节点距离压缩列表的起始地址有多少字节;
zllen属性:记录了压缩列表包含的节点数量;
entryX属性:压缩列表包含的各个节点;
zlend属性:用于标记压缩列表的末端。

压缩列表节点的结构

在这里插入图片描述

previous_entry_length属性:以字节为单位,记录压缩列表中前一个节点的长度。
程序可以通过指针运算,根据当前节点的起始地址来计算出前一个节点的起始地址,以此实现遍历操作。
encoding属性:记录了节点的content属性所保存数据的类型和长度;
content属性:保存节点的值,可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定。

连锁更新

假设压缩列表中所有节点的previous_entry_length属性都是用1字节来保存,那么节点的长度只要小于等于253字节previous_entry_length都可以记录,但是,如果添加一个长度大于253字节的节点,那么下一个节点的previous_entry_length就无法保存该长度的值,同样的,下下个节点也无法保存上个节点的长度,由此将导致连续多次空间扩展操作。

添加节点和删除节点都可能导致连锁更新,但是这种操作出现的几率很低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值