String(SDS):
与c语言不同。c语言在字符串的末尾使用空字符来表明一个字符串,即n个字符需要n+1的空间。
而redis使用了SDS----动态字符串,具有以下特点:
- 取字符串的长度仅需要 O(1)的时间复杂度
- 会为字符串预分配空间,使其append用时减少。而c语言在append时需要先查找空间。
- 避免字符串溢出。c语言在append时如果不进行空间查找和属性修改,便会直接在原字符串后进行append。若是原字符串限定了长度,会导致空间的溢出。而SDS在调用该API时会先修改其属性避免溢出
- 在字符串减少时使用惰性释放(时间局部性,空间局部性)。即假设现在把某个长度为10的字符串缩短为长度为5,那么多出的5个空间并不会立即被回收,而是等待一段时间。这段时间内加入再次更改这个字符串的值,那么刚好不需要再次查找新空间了
dict:
字典形,本质使用hashtable作为其底层表现
- 即为字典类型的数值。一个字典中包含了两张哈希表,而哈希表才是它存储数据的关键地方,其他属性之都是附庸:
哈希表[0]用于存放当前值,哈希表[1]用于实现哈希表的扩容操作(哈希表[1]相当于是个工具)。
rehashidx在不需要进行扩容或重散列时都是-1。当标为0时代表着扩容/重散列的开始。然后每当表[0]向表[1]中复制进一个数据时,rehashidx会自动加1.当扩容工作结束时,表[0]中每个hashEntry均->null,其原本的hashEtry均放在表[1]。此时再将此idx标为-1。
表的扩容是渐进的,一点点的来复制,此时若是有查,更,删的操作都会在两个表中进行,会先去表0中,若是没有找到再去表1。表0中能找到该元素说明此元素还没有进行复制到表1的操作,那么便不需要对表1进行更改
- 而哈希表中定义了一个由哈希对象组成的数组:
size是当前数组的大小,sizemask是哈希表大小的掩码值,总是等于size-1。
used是已有结点的数量。
- 而哈希对象的定义如下:
前两个代表着键和值,值可以是一个无符号整数,有符号数,64位浮点数或一个指针。
next是一个指针,指向下一个节点。
哈希表的扩展与收缩何时开始:当没有执行BGSAVE和BGREWRITEAOF指令(后台保存与后台更新,概括为写操作)时,负载因子大于1就会扩展。执行自动保存时,大于5会扩展。当负载因子小于0.1会进行收缩。(负载因子=used/totalsize)
哈希表:
intSet结构
首先介绍intset
intset可以存储整数值,其数值的范围是16,32,64位(即-2^(n-1)~2^(n-1)-1)
其一开始是定义为8位的数组,但其实并不存储8位,最小就是16位开始存
具体存的是几位数,由encoding记录。
intset的升级:由于一开始存入的是16位,所以当遇到过大的数字如6666666时,会将整个数组提升为32位,即encoding由INT16变为INT32。当存储一个更大的数字时会变为INT64。升级的过程中元素依然按照插入的顺序进行排列。这种做法使得数组的存储长度可变,更加灵活,但是并不会降级。
插入数值的源码:
升级的具体步骤:
先把总需要空间计算出来(一开始时三个16位,需要3*16=48的空间。插入一个较大数字时变为需要4*32=128位空间),将这些需要新增的空间全部分配给最后一个元素的位置
然后再对原有的数字进行搬迁,将其向后挪动
升级的时间复杂度位O(n),需要将每个元素均操作一遍。其过程类似于数组的插入,当再数组的头部插入一个数据时,也是类似的操作。只是redis中的intset还需要对其空间进行计算和分配
ziplist:
先看数据结构
规定ziplist要么存储数字要么存储字符串。
ziplist时压缩列表的简称。正如其名,它可以压缩一定的空间。ziplist时list和hashtable的底层实现之一,当list的键比较少,或者hash存储的值只有较小int和较短string时,会自动转换为ziplist。
- zlbytes:表明整个list占用的字节大小
- zltail:当前ziplist尾元素指针
- zllen:当前list的长度
- entry:可以存放一组数值或者字符串,分为三个部分:
- precious_entry_length:上一个节点的长度。用于从后向前遍历整个列表。它有一些有意思的特性,要么长度为1字节,要么长度为5字节
- encoding:有四种可能,根据其高字节两位来进行区分:00,01,10均为字节数组编码,意味着当前节点保存着字符型数组。11为整数
- 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位)------>……