redis的数据结构

 

String(SDS):

与c语言不同。c语言在字符串的末尾使用空字符来表明一个字符串,即n个字符需要n+1的空间。

而redis使用了SDS----动态字符串,具有以下特点:

  • 取字符串的长度仅需要 O(1)的时间复杂度
  • 会为字符串预分配空间,使其append用时减少。而c语言在append时需要先查找空间。
  • 避免字符串溢出。c语言在append时如果不进行空间查找和属性修改,便会直接在原字符串后进行append。若是原字符串限定了长度,会导致空间的溢出。而SDS在调用该API时会先修改其属性避免溢出
  • 在字符串减少时使用惰性释放(时间局部性,空间局部性)。即假设现在把某个长度为10的字符串缩短为长度为5,那么多出的5个空间并不会立即被回收,而是等待一段时间。这段时间内加入再次更改这个字符串的值,那么刚好不需要再次查找新空间了

dict:

字典形,本质使用hashtable作为其底层表现

  • 即为字典类型的数值。一个字典中包含了两张哈希表,而哈希表才是它存储数据的关键地方,其他属性之都是附庸:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

哈希表[0]用于存放当前值,哈希表[1]用于实现哈希表的扩容操作(哈希表[1]相当于是个工具)。

rehashidx在不需要进行扩容或重散列时都是-1。当标为0时代表着扩容/重散列的开始。然后每当表[0]向表[1]中复制进一个数据时,rehashidx会自动加1.当扩容工作结束时,表[0]中每个hashEntry->null,其原本的hashEtry均放在表[1]。此时再将此idx标为-1

表的扩容是渐进的,一点点的来复制,此时若是有查,更,删的操作都会在两个表中进行,会先去表0中,若是没有找到再去表1。表0中能找到该元素说明此元素还没有进行复制到表1的操作,那么便不需要对表1进行更改

 

  • 而哈希表中定义了一个由哈希对象组成的数组:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_8,color_FFFFFF,t_70,g_se,x_16

size是当前数组的大小,sizemask是哈希表大小的掩码值,总是等于size-1。

used是已有结点的数量。

  • 而哈希对象的定义如下:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_9,color_FFFFFF,t_70,g_se,x_16

前两个代表着键和值,值可以是一个无符号整数,有符号数,64位浮点数或一个指针。

next是一个指针,指向下一个节点。

哈希表的扩展与收缩何时开始:当没有执行BGSAVE和BGREWRITEAOF指令(后台保存与后台更新,概括为写操作)时,负载因子大于1就会扩展。执行自动保存时,大于5会扩展。当负载因子小于0.1会进行收缩。(负载因子=used/totalsize

哈希表:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_11,color_FFFFFF,t_70,g_se,x_16

 

intSet结构

首先介绍intset

41d3e946ec914f0a87c7b5e61c182127.png

intset可以存储整数值,其数值的范围是16,32,64位(即-2^(n-1)~2^(n-1)-1)

其一开始是定义为8位的数组,但其实并不存储8位,最小就是16位开始存

具体存的是几位数,由encoding记录。

intset的升级:由于一开始存入的是16位,所以当遇到过大的数字如6666666时,会将整个数组提升为32位,即encoding由INT16变为INT32。当存储一个更大的数字时会变为INT64。升级的过程中元素依然按照插入的顺序进行排列。这种做法使得数组的存储长度可变,更加灵活,但是并不会降级。

插入数值的源码:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_17,color_FFFFFF,t_70,g_se,x_16

升级的具体步骤:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

先把总需要空间计算出来(一开始时三个16位,需要3*16=48的空间。插入一个较大数字时变为需要4*32=128位空间),将这些需要新增的空间全部分配给最后一个元素的位置

然后再对原有的数字进行搬迁,将其向后挪动

升级的时间复杂度位O(n),需要将每个元素均操作一遍。其过程类似于数组的插入,当再数组的头部插入一个数据时,也是类似的操作。只是redis中的intset还需要对其空间进行计算和分配

 

ziplist:

先看数据结构

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

规定ziplist要么存储数字要么存储字符串。

ziplist时压缩列表的简称。正如其名,它可以压缩一定的空间。ziplist时list和hashtable的底层实现之一,当list的键比较少,或者hash存储的值只有较小int和较短string时,会自动转换为ziplist。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

  • zlbytes:表明整个list占用的字节大小
  • zltail:当前ziplist尾元素指针
  • zllen:当前list的长度
  • entry:可以存放一组数值或者字符串,分为三个部分:

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

  1. precious_entry_length:上一个节点的长度。用于从后向前遍历整个列表。它有一些有意思的特性,要么长度为1字节,要么长度为5字节

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYmVycnlfcGVuZw==,size_20,color_FFFFFF,t_70,g_se,x_16

  1. encoding:有四种可能,根据其高字节两位来进行区分:00,01,10均为字节数组编码,意味着当前节点保存着字符型数组。11为整数
  2. content:表明值。具体是unicode值还是数字由其前两位决定,总之存入的二进制数字

连锁更新:对ziplist中的某个元素更改时,可能会引发连锁更新。如本来entry1是一个总长度为254字节的节点,那么entry2的precious_entry_length只需要用1字节就可以,假设entry2也是一个254字节的长度,那么entry3也只需要1字节的precious_entry_length长度。

例如,当对entry1更改为一个更大的数字时,可能会导致其数值大于254,那么entry2便需要5位的precious_entry_length长度来存储它的长度。entry2会变为258长度。那么entry3也会需要5为来存precious_entry_length。

 

试着画图解释:

原来

Entry1(大小254)---->entry2(大小254,其中precious_entry_length占1位)----->entry3(大小254,其中precious_entry_length占1位)------>……..

更新entry1为257长度的一个元素导致:

Entry1(大小257)---->entry2(大小258,其中precious_entry_length由占用1位变为占用5位)---->entry3(大小258,其中precious_entry_length由占用1位变为占用5位)------>……

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值