redis zset
zset对象的结构可以是ziplist或skiplist
当同时满足以下两个条件时使用ziplist:
- 有序集合保存的元素小于128个
- 所有元素长度小于64字节
注,以上条件上限值可修改。在配置文件中zset-max-ziplsit-entries和zset-max-ziplist-value。
当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。集合元素按分值从小到大排序,小的在表头,大的在表尾。
zadd price 8.5 apple 8.1 banana
键price的值对象使用的是ziplist,对象如图所示。
对象使用的压缩列表如图所示:
当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系。
ziplist
ziplist使用压缩列表作为底层实现,每个元素用两个挨在一起的压缩列表节点来保存。第一个保存元素的成员member,第二个保存元素的分值score.
skiplist
skiplist底层使用zset结构实现。zset结构同时包含一个字典和一个跳跃表
typedef struct zset {
zskiplist *zsl; //跳跃表
dick *dick;
} zset;
zset结构中zsl跳跃表按分值从小到大保存所有集合元素,每个跳跃表节点保存1个集合元素:跳跃表节点的object属性保存元素的成员,跳跃表节点的score属性保存分值。通过跳跃表进行范围操作。
除此之外,zset结构中的dick字典属性为有序集合创建了一个成员-分值的映射,字典的键保存一个集合元素,值就是元素的分值。通过这个字典实现O(1)复杂度查找指定成员的分值,即zscore命令。
有序集合每个元素的成员是一个字符串对象,成员的分值是一个double类型的浮点数。
值得一提的是,set结构同时使用跳跃表和字典结构来保存有序集合元素,但是两个数据结构通过指针来共享相同元素的成员和分值,所以不会产生重复成员和分值,也不会浪费额外内存。
为什么要同时使用跳跃表和字典呢?因为如果只使用字典结构无法保证有序;如果只有跳跃表根据成员查找分值的时间复杂度将从O(1)升为O(logn)。