Redis中的数据结构 (15)

集合对象的底层实现有两种, 分别是intset和dict. 分别对应编码宏中的INTSET和HT. 显然当使用intset作为底层实现的数据结构时, 集合中存储的只能是数值数据, 且必须是整数. 而当使用dict作为集合对象的底层实现时, 是将数据全部存储于dict的键中, 值字段闲置不用.

集合对象的内存布局如下图所示:

setObject

集合对象的API接口如下:

分类 API名 功能
创建接口   robj *createSetObject(void)   创建一个空集合对象.
底层编码使用HT, 即底层使用dict
 
--   robj *createIntsetObject(void)   创建一个空集合对象.
底层编码使用INTSET, 即底层使用intset
 
--   robj *setTypeCreate(sds value)   创建一个空集合对象.
注意入参虽然携带了一个数据, 但这个数据并不会存储在集合中
这个数据只起到决定编码方式的作用, 若这个数据是数值的字符串表达, 则底层编码则为INTSET, 否则为HT
 
释放接口   void freeSetObject(robj *o)   释放集合对象.
若集合对象底层使用的是dict, 则调用dictRelease释放这个dict
若集合对象底层使用的是intset, 则直接释放这个intset占用的连续内存
 
编码转换接口   void setTypeConvert(robj *setobj, int enc)   转换集合对象的内部编码
虽然接口设计的好你可以在底层编码之间互相转换, 但实际上这个接口的实现, 目前仅支持从INTSET转换为HT
 
读写接口   int setTypeAdd(robj *subject, sds value)   向集合对象中写入一个数据  
--   int setTypeRemove(robj *setobj, sds value)   删除集合对象中的一个数据  
--   int setTypeIsMember(robj *subject, sds value)   判断指定数据是否在集合对象中  
--   int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele)   从集合对象中, 随机选出一个数据, 将其数据通过出参返回.
若数据是数值类型, 则从*llele返回, 否则, 从*sdsele返回.
注意该接口若取得二进制数据, 则*sdsele是直接引用集合内的数据, 而不是拷贝一份
 
--   unsigned long setTypeSize(const robj *subject)   返回集合中数据的个数  
迭代器接口   setTypeIterator *setTypeInitIterator(robj *subject)   创建一个集合对象迭代器  
--   void setTypeReleaseIterator(setTypeIterator *si)   释放集合对象迭代器  
--   int setTypeNext( setTypeIterator *si, sds *sdsele, int64_t *llele)   让集合迭代器步进一步, 并从出参中返回步进前迭代器所指向的数据.
若数据是数值类型, 则从*llele返回, 否则, 从*sdsele返回
注意该接口若取得二进制数据, 则*sdsele是直接引用集合内的数据, 而不是拷贝一份
 
--   sds setTypeNextObject(setTypeIterator *si)   让集合迭代器步进一步, 并把步进前所指向的数据, 拷贝一份, 构造成一个新的SDS, 作为返回值返回  
3.5 有序集合对象

有序集合的底层实现依然有两种, 一种是使用ziplist作为底层实现, 另外一种比较特殊, 底层使用了两种数据结构: dict与skiplist. 前者对应的编码值宏为ZIPLIST, 后者对应的编码值宏为SKIPLIST

使用ziplist来实现在序集合很容易理解, 只需要在ziplist这个数据结构的基础上做好排序与去重就可以了. 使用zskiplist来实现有序集合也很容易理解, Redis中实现的这个跳跃表似乎天然就是为了实现有序集合对象而实现的, 那么为什么还要辅助一个dict实例呢? 我们先看来有序集合对象在这两种编码方式下的内存布局, 然后再做解释:

首先是编码为ZIPLIST时, 有序集合的内存布局如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyzpsx.html