简单动态字符串(SDS)
自己构建的抽象类型,SDS用作Redis的默认字符串表示
C字符串用在打印日志这种无须对字符串值进行修改的地方
SDS的定义
SDS和C字符串的区别
-
有len,获取SDS长度的复杂度仅为O(1),C字符串需要遍历
-
SDS的API需要对SDS修改时,SDS会先检查SDS的空间是否满足修改所需的要求,不满足的话API会自动扩展SDS的空间,然后再执行,杜绝缓冲区溢出
-
SDS通过未使用空间实现了空间预分配和惰性空间释放两种优化策略,避免内存重分配
1.空间预分配
如果修改后SDS的长度小于1MB,那就len=free
如果修改后SDS长度大于等于1MB,那就分配1MB
通过空间预分配减少连续执行字符串增长操作所需的内存重分配次数
2.惰性空间释放
当SDS的PI需要缩短SDS保存的这些字符串时,程序不立即使用内存重分配,而是使用free属性将这些字节的数量记录起来,等待将来使用
-
SDS使用len属性的值而不是空字符来判断字符串是否结束。SDS的API都是二进制安全的,所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据
-
SDS遵循C字符串以空字符串结尾的惯例,所以兼容部分C字符串函数,可以使用一部分<string.h>库中的函数
链表
除了链表键以外,发布与订阅、慢查询、监视器等功能也用到了链表
Redis链表特性
字典
又称符号表、关联数组或映射,是一种用于保存键值对的抽象数据结构
字典中的每个键都是独一无二的
Redis构建了自己的字典实现,应用广泛,Redis的数据库就是使用字典来作为底层实现的
字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都是比较长的字符串时,Redis就会使用字典作为哈希键的底层实现
哈希表
table属性是一个数组,数组中的每个元素都是一个指向dict.h/dicEntry结构的指针,每个dictEntry结构保存着一个键值对
size记录了哈希表的大小,也即是每个table数组的大小
used属性记录了哈希表目前已有节点(键值对)的数量
sizemak属性等于size-1,和哈希值一起决定一个键应该被放到table数组的哪个索引上面
哈希表节点
字典
type属性和privdate属性是针对不同类型的键值对,为创建多态字典而设置的
type属性是一个指向dictType结构的指针,每个dictType结构保存了一簇用于操作特定类型键值对的函数
ht属性是一个包含两个项的数组,数组中的每个项都是一个dictht哈希表,一般情况下字典只使用ht[0]哈希表,ht[1]哈希表只会在对ht[0]哈希表进行rehash时使用
rehshidx记录了rehash目前的进度,如果没有在进行rehash,那么它的值为-1
哈希算法
解决键冲突
rehash
当哈希表保存的键值对数量太多或者太少时,程序需要对哈希表的大小进行相应的扩展或者收缩
渐进式rehash
分而治之,避免了集中式rehash带来的庞大计算量
在渐进式rehash期间,字典的删除、查找、更新、等操作会在两个哈希表上进行
新添加到字典的键值对一律会被保存到ht[1]里面
跳跃表
跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点
Redis使用跳跃表作为有序集合键值的底层实现之一,如果一个有序集合包含的元素数量比较多,又或则有序集合中元素的成员
Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构
跳跃表节点
-
层
level数组可以包含多个元素,每个元素包含一个指向其他节点的指针,一般来说层的数量越多,访问其他节点的速度就越快
-
前进指针
每个层都有一个指向表尾方向的前进指针,用于从表头向表尾访问节点
-
跨度
层的跨度用于记录两个节点之间的距离
两个节点之间的跨度越大,它们相距就越远
指向NULL的所有前进指针的跨度都为0,因为它们没有连向任何节点
跨度实际上是用来排位的,遍历操作只使用前进指针就可以完成了
-
后退指针
用于从表尾向表头方向访问节点,每个节点只有一个后退指针,每次只能后退前一个节点
-
分值和成员
分值是一个double类型的浮点数,跳跃表中的所有节点都按分值从小到大来排序
节点的成员对象是一个指针,它指向一个字符串对象,而字符串对象则保存着一个SDS值
同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值可以是相同的,成员对象较小的节点会排在前面
跳跃表
header和tail指针分别指向跳跃表的表头和表尾节点,通过这两个指针,程序定位表头结点和表尾节点的复杂度为O(1)
重点回顾
整数集合
整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,Redis就会使用整数集合作为集合键的底层实现
整数集合的实现
整数集合的每个元素都是contents数组的一个数组项,各个项在数组中按值的大小从小到大有序地排列,并且数组中不包含任何重复项
length属性记录了整数集合包含的元素数量,也即是contents数组的长度
contents数组的真正类型取决于encoding属性的值
整数集合的升级
升级例子的前几步:
向整数集合添加新元素的时间复杂度为O(N)
升级的好处
一个是提升整数集合的灵活性,另一个是尽可能地节约内存
不支持降级
压缩列表
压缩列表是列表键和哈希键的底层实现之一
当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现
当哈希键只包含少量键值对,且每个键值对的键和值要么是小整数值,要么是长度比较短的字符串,那么Redis就会使用压缩列表来做哈希键的底层实现
压缩列表的构成
压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构
一个压缩列表可以包含任意多个节点,每个节点可以保存一个字节数组或者已给整数值