Redis底层数据结构

五种基本类型

Redis是一个键值对数据库,而键都是字符串(Redis内部实现的简单动态字符串)类型,值有5种基本类型,分别是:

  • STRING(字符串)
  • LIST(列表)
  • SET(集合)
  • ZSET(有序集合)
  • HASH(哈希)
    在这里插入图片描述

1. String

在这里插入图片描述

  • 如存储的是整数型值,比如set num 123这样的类型,就会使用 int 的存储方式进行存储,在redisObject的ptr属性中就会保存该值。
  • 存储的字符串是一个字符串值并且长度大于32个字节就使用SDS(simple dynamic string)方式进行存储,并且encoding设置为raw;
  • 若字符串长度小于等于32个字节就将encoding改为embstr来保存字符串。

SDS称为简单动态字符串,对于SDS中的定义在Redis的源码中有的三个属性:int lenint freechar buf[]

  • len保存了字符串的长度
  • free表示buf数组中未使用的字节数量
  • buf数组则是保存字符串的每一个字符元素。

因此当你在Redsi中存储一个字符串Hello时,根据Redis的源代码的描述可以画出SDS的形式的redisObject结构图如下图所示:
在这里插入图片描述
上面的定义相对于 C 语言对于字符串的定义,多出了 len 属性以及 free 属性。为什么不使用C语言字符串实现,而是使用 SDS呢?这样实现有什么好处?
(1)c语言中的字符串并不会记录自己的长度,因此「每次获取字符串的长度都会遍历得到,时间的复杂度是O(n)」,而Redis中获取字符串只要读取len的值就可,时间复杂度变为O(1)。

(2)「c语言」中两个字符串拼接,若是没有分配足够长度的内存空间就「会出现缓冲区溢出的情况」;而「SDS」会先根据len属性判断空间是否满足要求,若是空间不够,就会进行相应的空间扩展,所以「不会出现缓冲区溢出的情况」。

(3)SDS还提供「空间预分配」和「惰性空间释放」两种策略。在为字符串分配空间时,分配的空间比实际要多,这样就能「减少连续的执行字符串增长带来内存重新分配的次数」。

当字符串被缩短的时候,SDS也不会立即回收不适用的空间,而是通过free属性将不使用的空间记录下来,等后面使用的时候再释放。

具体的空间预分配原则是:「当修改字符串后的长度len小于1MB,就会预分配和len一样长度的空间,即len=free;若是len大于1MB,free分配的空间大小就为1MB」。

(4)SDS是二进制安全的,除了可以储存字符串以外还可以储存二进制文件(如图片、音频,视频等文件的二进制数据);而c语言中的字符串是以空字符串作为结束符,一些图片中含有结束符,因此不是二进制安全的。
在这里插入图片描述

2. List

Redis中的 List 在3.2之前的版本是使用 ziplist 和 linkedlist 进行实现的。在3.2之后的版本就是引入了 quicklist。

2.1 linkedlist

是一个双向链表,他和普通的链表一样都是由指向前后节点的指针。插入、修改、更新的时间复杂度尾O(1),但是查询的时间复杂度确实O(n)。

linkedlist 和 quicklist 的底层实现是采用链表进行实现,在c语言中并没有内置的链表这种数据结构,Redis 实现了自己的链表结构。
在这里插入图片描述

Redis中链表的特性:

  • 每一个节点都有指向前一个节点和后一个节点的指针。
  • 头节点和尾节点的prev和next指针指向为null,所以链表是无环的。
  • 链表有自己长度的信息,获取长度的时间复杂度为O(1)。

2.2 ziplist

压缩列表(ziplist)是一组连续内存块组成的顺序的数据结构,压缩列表能够节省空间,压缩列表中使用多个节点来存储数据。

压缩列表是列表键和哈希键底层实现的原理之一,压缩列表并不是以某种压缩算法进行压缩存储数据,而是它表示一组连续的内存空间的使用,节省空间,压缩列表的内存结构图如下:
在这里插入图片描述

压缩列表中每一个节点表示的含义如下所示:

  1. zlbytes:4个字节的大小,记录压缩列表占用内存的字节数。
  2. zltail:4个字节大小,记录表尾节点距离起始地址的偏移量,用于快速定位到尾节点的地址。
  3. zllen:2个字节的大小,记录压缩列表中的节点数。
  4. entry:表示列表中的每一个节点。
  5. zlend:表示压缩列表的特殊结束符号’0xFF’。

每一个entry节点又有三部分组成,包括previous_entry_engthencodingcontent

  • previous_entry_ength表示前一个节点entry的长度,可用于计算前一个节点的其实地址,因为他们的地址是连续的。
  • encoding:这里保存的是content的内容类型和长度。
  • content:content保存的是每一个节点的内容。

3. Hash类型

Hash对象的实现方式有两种分别是 ziplist、hashtable,其中hashtable的存储方式key是String类型的,value也是以 (key, value) 的形式进行存储。

3.1 ziplist

当数据量比较小,或者单个元素比较小时,底层用ziplist存储:一个entry存储key,一个entry存储value

3.2 hashtable

当数据量较大时:Redis底层数据结构之Hash

4. Set

Set集合是无序的,ZSet是有序集合。

  • Set的底层实现是 hashtable 和 intset
  • 当数据可以用整形表示时,Set集合将被编码为intset数据结构
  • 两个条件任意满足时Set将用 hashtable 存储数据(value为 null):
    1. 元素个数大于set-max-intset-entries(intset能存储的元素个数上限)
    2. 元素无法用整形表示

4.1 inset

inset也叫做整数集合,用于保存整数值的数据结构类型,它可以保存int16_t、int32_t 或者int64_t 的整数值。
在这里插入图片描述

  • 三个属性值encoding、length、contents[],分别表示编码方式、整数集合的长度、以及元素内容,length就是记录contents里面的大小。
  • contents有序存储,使用二分查找(logn)查找元素在不在集合中

在整数集合新增元素的时候,若是超出了原集合的长度大小,就会对集合进行升级,具体的升级过程如下:

首先扩展底层数组的大小,并且数组的类型为新元素的类型。
然后将原来的数组中的元素转为新元素的类型,并放到扩展后数组对应的位置。
整数集合升级后就不会再降级,编码会一直保持升级后的状态。

4.2 hashtable

5. ZSet

在这里插入图片描述
ZSet为有序的,自动去重的集合数据类型,ZSet 数据结构底层实现为字典(dict) +跳表(skiplist) ,当数据比较少时,用ziplist编码结构存储。

zset-max-ziplist-entries 128 //元素个数超过128,将用skiplist编码
zset-max-ziplist-value 64 // 单个元素大小超过64 byte,将用skiplist编码

5.1 ziplist编码

  • ziplist编码的有序集合使用紧挨在一起的压缩列表节点来保存,第一个节点保存member,第二个保存score。
  • ziplist内的集合元素按score从小到大排序,score较小的排在表头位置。

5.2 skiplist编码

  • skiplist编码的有序集合底层是一个命名为zset的结构体,而一个zset结构包含一个字典和一个跳跃表。
  • 跳跃表按score从小到大保存所有集合元素。
  • 字典则保存着从member到score的映射,这样就可以用O(1)的复杂度来查找member对应的score值。
  • 虽然同时使用两种结构,但它们会通过指针来共享相同元素的member和score,因此不会浪费额外的内存。

跳表的原理

  • 跳表查找:时间复杂度为O(logN)
  • 跳表插入数据:时间复杂度是O(logN);可以选择同时将这个数据插入到第几层中,比如随机函数生成了值 K,那我们就将这个结点添加到第一层到第 K 层这 K 级索引中。
    随机的K产生:通过随机数的方式,第一层肯定需要添加元素,所以K的初始值为1。后面的,如果随机数为1,就是K加一,随机数为0,就退出。这样每一层插入该元素的概率为(1/2)^ n。这样就很大程度上保证了后一层元素的总数量是前一层元素的2倍。
int random_level()  
{  
    K = 1;  

    while (random(0,1))  
        K++;  

    return K;  
}  
  • 跳表删除:时间复杂度是O(logN);在各个层中找到包含 x 的节点,使用标准的 delete from list 方法删除该节点。直接删除元素,然后调整一下删除元素后的指针即可。跟普通的链表删除操作完全一样。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值