标题首先明确一下几点基本常识:
- Redis常见的5种数据类型:String(字符串)、List(列表)、Hash(哈希)、Set(集合)、ZSet(有序集合),这五种常见的数据类型,本质上对应着五种对象,即字符串对象、列表对象、哈希对象、集合对象、有序集合对象。
- 在Redis中,任意一个对象都具有五种属性
[1] type ====> 类型对应基本数据类型。例如Redis_String 对应字符串,Redis_List对应列表
[2] encoding ====> 编码。编码方式决定了对象的底层的数据结构,一个对象至少有两种编码方式
[3] prt ====> 指针。指向由编码决定的数据结构,数据结构中往往包含有所存的数据
[4] refcount ====> 引用计数。这个属性主要是为了实现redis中的内存回收机制
[5] lru ====> 最近最少使用。用来解决对象的空转时长,同时也会被用于当缓冲达到最大值,再向其中添加数据时,应该删除什么数据。
有序结合(ZSet)对象详解
上述说过,一个对象的底层的数据结构是由对象的编码方式决定的,而ZSet的编码方式有两种
- ziplist
- skiplist
上述两种数据结构的底层实现见书籍 《redis设计与实现》。
有序结合底层s
ZipList
使用压缩连表时,要保证集合中的数据有序,会将key放在前面一位,然后将key所对应value放在key的后一位。这样就能够保证集合的有序。
SkipList
使用跳跃链表时,由于跳跃链表本来就是有序的,直接使用即可。
跳表数据结构见《Redis设计与实现》P39
当有序集合对象同时满足以下两个条件时,对象使用压缩链表编码:
- 有序集合保存的元素数量小于128个;
- 有序集合保存的所有元素成员的长度都小于64字节;
如果不满足上述两个条件,那么ZipList会转化为SkipList,同时,当后面的SkipList的元素数量和元素成员的长度满足要求时,也不会回退为ZipList。
重点
当使用SkipList实现ZSet时,ptr所指向的zset数据结构中包含了一个skiplist和dict结构。其中dict结构中创建了一个从SkipList对象到score的映射。通过dict可以实现O(1)时间复杂度查找key所对应的value。
那么为什么不直接使用字典或者skipList实现Zset呢?
- 当直接使用字典的时候,虽然可以做到查询key-value的时间复杂度O(1),但是字典本身是无序的,要排序至少也需要O(Nlog(N))。
- 当直接使用SkipList时,虽然可以做到有序,但是查询key-value至少也需要O(logN)。