这次把两个set放在一起来说吧。经过前面的hash与list的分析,这两个的实现感觉其实比前面的要简单一些的。
首先来说一下这个t_set吧。这个set的实现主要是由intset与dict两种编码模式实现的。其中intset只能存储整型,而且是有序存储的。而dict就是前面熟知的字典实现的hash。来看看一些set的基本实现吧。
从其创建可以看到,如上所说的也就两种编码模式。
/* Factory method to return a set that *can* hold "value". When the object has
* an integer-encodable value, an intset will be returned. Otherwise a regular
* hash table. */
robj *setTypeCreate(sds value) {
//setType总共两种类型 intset或者 dict
//intset是有序的
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject();
return createSetObject();
}
根据类型来添加元素,而使用dict的话会浪费key-value中的一个键位。只使用其中一个空间进行存储。
/* Add the specified value into a set.
*
* If the value was already member of the set, nothing is done and 0 is
* returned, otherwise the new element is added and 1 is returned. */
int setTypeAdd(robj *subject, sds value) {
//根据类型添加元素
long long llval;
if (subject->encoding == OBJ_ENCODING_HT) {
dict *ht = subject->ptr;
//若为hash编码则在集合中新加value 新键
dictEntry *de = dictAddRaw(ht,value,NULL);
if (de) {
//设置key与val的值
dictSetKey(ht,de,sdsdup(value));
dictSetVal(ht,de,NULL);
return 1;
}
} else if (subject->encoding == OBJ_ENCODING_INTSET) {
//若编码为intset 那么将value转化为整型
if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
//将llval加入到ptr所指的intset数据结构中
if (success) {
/* Convert to regular set when the intset contains
* too many entries. */
//添加成功后则看看有没有超出最大值
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
setTypeConvert(subject,OBJ_ENCODING_HT);
return 1;
}
} else {
//若不能转化为整型,那么将subject转成dict
//然后添加value - null 键值对
/* Failed to get integer from object, convert to regular set. */
setTypeConvert(subject,OBJ_ENCODING_HT);
/* The set *was* an intset and this value is not integer
* encodable, so dictAdd should always work. */
serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);
return 1;
}
} else {
serverPanic("Unknown set encoding");
}
return 0;
}
删除元素
int setTypeIsMember(robj *subject, sds value) {
//查找value在subject是否存在存在返回1 否则返回0
long long llval;
if (subject->encoding == OBJ_ENCODING_HT) {
return dictFind((dict*)subject->ptr,value) != NULL;
} else if (subject->encoding == OBJ_ENCODING_INTSET) {
if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {
return intsetFind((intset*)subject->ptr,llval);
}
} else {
serverPanic("Unknown set encoding");
}
return 0;
}
set的迭代器创建
setTypeIterator *setTypeInitIterator(robj *subject) {
/*
typedef struct {
robj *subject;
int encoding;
int ii; //intset iterator
dictIterator *di;
} setTypeIterator;
set迭代器结构体。
*/
//下面根据类型初始化结构体
setTypeIterator *si = zmalloc(sizeof(setTypeIterator));
si->subject = subject;
si->encoding = subject->encoding;
if (si->encoding == OBJ_ENCODING_HT) {
si->di = dictGetIterator(subject->ptr);
} else if (si->encoding == OBJ_ENCODING_INTSET) {
si->ii = 0;
} else {
serverPanic("Unknown set encoding");
}
return si;
}
迭代器的next函数
int setTypeNext(setTypeIterator *si, sds *sdsele, int64_t *llele) {
//不成功则返回-1 否则返回迭代器的编码类型
if (si->encoding == OBJ_ENCODING_HT) {
//若编码为dict则调用dict的api获得值
//而在set使用dict存储 所有的值存在key value为null
dictEntry *de = dictNext(si->di);
if (de == NULL) return -1;
*sdsele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */
//将不使用的llele初始化
} else if (si->encoding == OBJ_ENCODING_INTSET) {
//若为intset则通过si->ii判定当前迭代器指向第几个元素
if (!intsetGet(si->subject->ptr,si->ii++,llele))
return -1;
*sdsele = NULL; /* Not needed. Defensive. */
} else {
serverPanic("Wrong set encoding in setTypeNext");
}
return si->encoding;
}
以sds返回set中的元素
sds setTypeNextObject(setTypeIterator *si) {
int64_t intele;
sds sdsele;
int encoding;
//调用上面的函数,返回的必定为sds字符串
encoding = setTypeNext(si,&sdsele,&intele);
switch(encoding) {
case -1: return NULL;
case OBJ_ENCODING_INTSET:
return sdsfromlonglong(intele);
case OBJ_ENCODING_HT:
return sdsdup(sdsele);
default:
serverPanic("Unsupported encoding");
}
return NULL; /* just to suppress warnings */
}
set中随机获取元素,返回该元素的编码方式。
int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
//随机获得一个set中的值,然后保存在参数中,返回集合的编码类型
if (setobj->encoding == OBJ_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(setobj->ptr);
*sdsele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */
} else if (setobj->encoding == OBJ_ENCODING_INTSET) {
*llele = intsetRandom(setobj->ptr);
*sdsele = NULL; /* Not needed. Defensive. */
} else {
serverPanic("Unknown set encoding");
}
return setobj->encoding;
}
最后来看一下编码转换到函数吧。
void setTypeConvert(robj *setobj, int enc) {
//将set的编码从 intset转换为dict编码类型
setTypeIterator *si;
serverAssertWithInfo(NULL,setobj,setobj->type == OBJ_SET &&
setobj->encoding == OBJ_ENCODING_INTSET);
if (enc == OBJ_ENCODING_HT) {
int64_t intele;
dict *d = dictCreate(&setDictType,NULL);
sds element;
/* Presize the dict to avoid rehashing */
dictExpand(d,intsetLen(setobj->ptr));
//插入元素前,首先将容量变成set的长度
/* To add the elements we extract integers and create redis objects */
si = setTypeInitIterator(setobj);
//调用迭代器,将所有元素一个个遍历,然后添加到字典中
while (setTypeNext(si,&element,&intele) != -1) {
element = sdsfromlonglong(intele);
serverAssert(dictAdd(d,element,NULL) == DICT_OK);
}
setTypeReleaseIterator(si);
//释放迭代器
setobj->encoding = OBJ_ENCODING_HT;
//重设编码类型
zfree(setobj->ptr);
//释放空间
setobj->ptr = d;
//重设指针
} else {
serverPanic("Unsupported set conversion");
}
}
剩下的都是command,其逻辑与实现都不难。有兴趣可以自己看看就好了。
set的实现大概明了后,来看看zset的实现方式吧。zset的话是整个类型都是以有序的方式存储的。
zset分为两种编码类型。一种是以ziplist用来存储方式,以ele,score一对一对存储,根据score进行排序。另外一种是使用zset结构体来存储。
typedef struct zset {
dict *dict;
//用于维护 value与score 的映射
zskiplist *zsl;
//按score 对元素进行排序,使得为O(logN)
} zset;
zset结构体定义
zset这个结构体,分为dict与跳表两个元素。dict存储value与score组成的键值对,而zskiplist则是以score进行排序的链表。
其实现的api大多数都是逻辑简单,调用前面的基础api进行实现的,存在大量重复的东西,就不细细分析了。