目录
sortedset同时会由两种数据结构支持,ziplist
和skiplist
.
只有同时满足如下条件是,使用的是ziplist
,其他时候则是使用skiplist
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素的长度小于64字节
ziplist
当ziplist
作为存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表结点来保存,
第一个节点保存元素的成员,第二个元素保存元素的分值.
ziplist 是一个特殊的双向链表
特殊之处在于:没有维护双向指针:prev next;而是存储上一个 entry的长度和 当前entry的长度,通过长度推算下一个元素在什么地方。
牺牲读取的性能,获得高效的空间存储,因为(简短字符串的情况)存储指针比存储entry长度 更费内存。这是典型的“空间换时间”。
ziplist的主要优点是节省内存,但它查找操作只能按顺序查找(可以是从前往后、也可以从后往前)。
ziplist将数据按照一定规则编码在一块连续的内存区域,目的是节省内存,这种结构并不擅长做修改操作。一旦数据发生改动,就会引发内存realloc,可能导致内存拷贝。
ziplist使用局限性
字段、值比较小,才会用ziplist。
当使用skiplist
作为存储结构时,使用skiplist
按序保存元素分值,使用dict
来保存元素和分值的对应关系
redisDb 整体使用 dict字典 来存储键值对KV;
字典中的每一项,使用dictEntry ,代表KV键值;类似于HashMap中的键值对Entry。
跳跃表(skiplist)新增
- 新节点和各层索引节点逐一比较,确定原链表的插入位置。O(logN)
- 把索引插入到原链表。O(1)
- 利用抛硬币的随机方式,决定新节点是否提升为上一级索引。结果为“正”则提升并继续抛硬币,结果为“负”则停止。O(logN)
第一次抛硬币为正
硬币结果为正,插入的节点提拔为上一层索引
提拔为索引节点的数据,继续抛硬币,结果为正,则把该索引节点继续提升为上层节点
第二次抛硬币
为反面
为正面
为什么使用抛硬币的方式
1. 跳跃表删除和添加节点是不可预测的,很难用一种有效的算法来保证跳跃表的索引始终均匀
2. 随机抛硬币的方式虽然不能保证索引均匀分布,却可以让大体的数据结构趋于均匀
跳跃表删除
- 自上而下,查找第一次出现节点的索引,并逐层找到每一层对应的节点。O(logN)
- 删除每一层查找到的节点,如果该层只剩下1个节点,删除整个一层(原链表除外)。O(logN)
跳跃表&B+树优劣
跳跃表的优点:
维持结构平衡的成本比较低,完全依靠随机
B+树
在多次插入删除后,需要rebalance来重新调整结构平衡