跳跃表
跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)、最坏O(N)复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。
Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。
和链表、字典等数据结构被广泛地应用在Redis内部不同,Redis只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是集群节点中用作内部数据结构,除此之外,跳跃表在Redis里面没有其他用途。
结构
- level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内)
-
- 跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。
-
- 每次创建一个新跳跃表节点的时候,程序根据幂次定律(power law,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”
- length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内)
- 分数(score):各个节点中的1.0、2.0和3.0是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。
- 跨度:层的跨度(level[i].span属性)用于记录两个节点之间的距离
插入元素
链表的插入和删除是结合搜索过程完成的。在一个跳表中插入元素17的过程如下:
删除元素
删除元素1的过程如下:
整数集合
整数集合(intset)是集合键的底层实现之一,当一个集合中只包含整数值元素,并且这个集合的元素数量不多时,Redis 就会使用整数集合作为集合键的底层实现。
整数集合的底层是数组,有序、无重复。
实现
结构如图所示:
contents 数组是整数集合的底层实现。虽然 contents 属性声明为 int8_t 类型的数组,但实际上contents数组并不保存任何 int8_t 类型的值,contents 数组的真正类型取决于 encoding 属性的值。encoding 的值可以为:INTSET_ENC_INT16、INTSET_ENC_INT32、INTSET_ENC_INT64。
升级
当加入一个新元素时,如果元素类型比集合内的所有元素类型都长,整数集合会进性升级。步骤如下:
- 扩展空间。根据新元素的类型,扩展空间,包括新元素的空间
- 移动旧元素。
- 放入新元素。改编码格式。
在一个 INTSET_ENC_INT16 编码的集合中,有三个元素,现在放入65535(INTSET_ENC_INT32):
元素放入之后,要保证集合有序。
整数数组不支持降级操作。
提升灵活性
底层自动修改属性,不用担心类型错误。
节约内存
可以不用直接分配 int32 或 int64 类型的数组,有需要时才分配,避免空间浪费。
压缩列表
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
压缩列表的遍历是从表尾向表头遍历。
结构实现
- previous_entry_length:previous_entry_length属性以字节为单位,记录了压缩列表中前一个节点的长度
-
- 如果前一节点的长度小于254字节,那么previous_entry_length属性的长度为1字节
-
- 如果前一节点的长度大于等于254字节,那么previous_entry_length属性的长度为5字节:其中属性的第一字节会被设置为0xFE(十进制254),而之后的四个字节则用于保存前一节点的长度。
- encoding:节点的encoding属性记录了节点的content属性所保存数据的类型以及长度
-
- 一字节、两字节或者五字节长,值的最高位为00、01或者10的是字节数组编码:这种编码表示节点的content属性保存着字节数组,数组的长度由编码除去最高两位之后的其他位记录;
-
- 一字节长,值的最高位以11开头的是整数编码:这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他位记录;
- 一字节长,值的最高位以11开头的是整数编码:这种编码表示节点的content属性保存着整数值,整数值的类型和长度由编码除去最高两位之后的其他位记录;
- content:节点的content属性负责保存节点的值,节点值可以是一个字节数组或者整数,值的类型和长度由节点的encoding属性决定
连锁更新
添加删除节点,均可能导致连锁更新。
在最坏的情况下,连锁更新的时间复杂度为O(n2)。但极端情况出现概率极小。平均复杂度为O(N)。