redis学习系列(三-5)--redis基础类型初探(有序集合对象)
有序集合对象使用的编码是ziplist或者skiplist
ziplist
有序结合的ziplist使用的是压缩列表作为底层实现,每个集合元素使用的紧靠的压缩列表节点存储,第一个节点保存成员,第二个元素保存元素的分值。
因此压缩列表内集合元素按照分值大小排序,分值较小放置靠近表头,分值较大放置在表尾的位置
下例子中分值是 8 6 7 4
127.0.0.1:6379> zadd p 8 apple 6 ba 7 ch 4 uu
(integer) 4
127.0.0.1:6379> type p
zset
127.0.0.1:6379> zrange p 0 -1
1) "uu"
2) "ba"
3) "ch"
4) "apple"
127.0.0.1:6379> obejct encoding p
(error) ERR unknown command 'obejct'
127.0.0.1:6379> object encoding p
"ziplist"
127.0.0.1:6379>
skiplist
skiplist编码的有序集合对象使用的是zset结构作为底层实现,一个zet包含一个字典和一个跳跃表
typedef struct zset { zskiplist *zsl; dict *dict; } zset;咋读一遍,我大概知道了 *zsl便于 范围操作, 而 *dict 便于精确查询
*zsl
跳跃表,按照分值大小从小到大保存所有集合元素,每个跳跃表都保存了一个集合元素,跳跃表节点的object属性保存了元素的成员,节点的score保存了元素的分值,通过此
可以进行范围操作,例如zrange
*dict
dict为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个元素集合,键保存了元素的成员,字典的值保存了元素的分值,因此通过这个字典,程序可以用O(1)复杂度查询到指定成员的分值,ZSCORE就是根据这个特性实现的
有序集合每个元素的成员都是一个字符串对象,每个元素的分值都是一个double类型的浮点数,值得一提的是虽然使用了两种结构来保存数据,但是两种结构都会通过指针来共享相同元素的成员和分值,因此不会浪费额外的内存
另外书中也介绍了为什么同时使用了跳跃表和字典来实现有序集合
1.如果单纯使用跳跃表,那么跳跃表的有点会存在,也就是范围操作会快很多,但是没有了字典,通过分值查询这一操作的复杂度将从O(1)上升为O(logN)
2.如果单纯使用字典,那么分值查询会很快,O(1),但是范围操作会需要先执行排序,至少需要O(N\logN),另外需要额外的O(N)的内存空间,不合算。
至于跳跃表,内部实现有点复杂,暂时不去看了,知道一下
另外copy了一张网图,便于理解,其实跳跃表比一般单链表快的原因,就是它能跳跃着寻找这个元素,而不是传统的O(n)。
Redis作者为了适合自己功能的需要,对原来的跳跃表进行了一下修改:
1、 允许重复的score值:多个不同的元素(member)的score值可以相同
2、 进行元素对比的时候,不仅要检查score值,还需要检查member:当score值相等时,需要比较member域进行比较。
3、 结构保存一个tail指针:跳跃表的表尾指针
4、 每个节点都有一个高度为1层的前驱指针,用于从底层表尾向表头方向遍历
编码的转换
ziplist使用条件:
1.有序集合保存的数量小于128个,
2.有序集合保存的所有元素的长度都小于54字节。
可以通过配置文件zset-max-ziplist-entries 选项和 zset-max-ziplist-value 来修改配置
因为有序集合键的值为有序集合对象, 所以用于有序集合键的所有命令都是针对有序集合对象来构建的, 表 8-11 列出了其中一部分有序集合键命令, 以及这些命令在不同编码的有序集合对象下的实现方法。
表 8-11 有序集合命令的实现方法
命令 |
ziplist 编码的实现方法 |
zset 编码的实现方法 |
---|---|---|
ZADD |
调用 ziplistInsert 函数, 将成员和分值作为两个节点分别插入到压缩列表。 |
先调用 zslInsert 函数, 将新元素添加到跳跃表, 然后调用 dictAdd 函数, 将新元素关联到字典。 |
ZCARD |
调用 ziplistLen 函数, 获得压缩列表包含节点的数量, 将这个数量除以 2 得出集合元素的数量。 |
访问跳跃表数据结构的 length 属性, 直接返回集合元素的数量。 |
ZCOUNT | 遍历压缩列表, 统计分值在给定范围内的节点的数量。 | 遍历跳跃表, 统计分值在给定范围内的节点的数量。 |
ZRANGE | 从表头向表尾遍历压缩列表, 返回给定索引范围内的所有元素。 | 从表头向表尾遍历跳跃表, 返回给定索引范围内的所有元素。 |
ZREVRANGE | 从表尾向表头遍历压缩列表, 返回给定索引范围内的所有元素。 | 从表尾向表头遍历跳跃表, 返回给定索引范围内的所有元素。 |
ZRANK | 从表头向表尾遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 | 从表头向表尾遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 |
ZREVRANK | 从表尾向表头遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 | 从表尾向表头遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 |
ZREM | 遍历压缩列表, 删除所有包含给定成员的节点, 以及被删除成员节点旁边的分值节点。 | 遍历跳跃表, 删除所有包含了给定成员的跳跃表节点。 并在字典中解除被删除元素的成员和分值的关联。 |
ZSCORE | 遍历压缩列表, 查找包含了给定成员的节点, 然后取出成员节点旁边的分值节点保存的元素分值。 | 直接从字典中取出给定成员的分值。 |