??学习完 t_string.c、t_list.c、t_hash.c文件后,现在开始学习 t_set.c 的代码,从文件名可以看到是相关集合相关命令的代码文件。总共5种数据结构,我们已经学习到第4个了,离成功不远了,再加一把劲加油。
1 saddCommand
1.1 方法说明
??向一个集合中添加元素。
1.2 命令实践
??添加成功返回添加元素的个数。
1.3 命令源代码
void saddCommand(redisClient *c) {
robj *set;
int j, added = 0;
set = lookupKeyWrite(c->db,c->argv[1]);
if (set == NULL) {
set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
} else {
if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr);
return;
}
}
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
if (setTypeAdd(set,c->argv[j])) added++;
}
if (added) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
server.dirty += added;
addReplyLongLong(c,added);
}
1.4 相关源代码
1.4.1 setTypeCreate
robj *setTypeCreate(robj *value) {
if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
return createIntsetObject();
return createSetObject();
}
1.4.2 setTypeAdd
int setTypeAdd(robj *subject, robj *value) {
long long llval;
if (subject->encoding == REDIS_ENCODING_HT) {
if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
incrRefCount(value);
return 1;
}
}
else if (subject->encoding == REDIS_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
if (success) {
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
setTypeConvert(subject,REDIS_ENCODING_HT);
return 1;
}
} else {
setTypeConvert(subject,REDIS_ENCODING_HT);
redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
incrRefCount(value);
return 1;
}
} else {
redisPanic("Unknown set encoding");
}
return 0;
}
1.4.3 setTypeConvert
void setTypeConvert(robj *setobj, int enc) {
setTypeIterator *si;
redisAssertWithInfo(NULL,setobj,setobj->type == REDIS_SET &&
setobj->encoding == REDIS_ENCODING_INTSET);
if (enc == REDIS_ENCODING_HT) {
int64_t intele;
dict *d = dictCreate(&setDictType,NULL);
robj *element;
dictExpand(d,intsetLen(setobj->ptr));
si = setTypeInitIterator(setobj);
while (setTypeNext(si,NULL,&intele) != -1) {
element = createStringObjectFromLongLong(intele);
redisAssertWithInfo(NULL,element,dictAdd(d,element,NULL) == DICT_OK);
}
setTypeReleaseIterator(si);
setobj->encoding = REDIS_ENCODING_HT;
zfree(setobj->ptr);
setobj->ptr = d;
} else {
redisPanic("Unsupported set conversion");
}
}
1.5 代码理解
- 先获取获取集合键对象。
- 判断集合是否存在。
- 集合如果不存在,则创建一个集合。
- 集合如果存在,则判断对象类型是否集合。
- 遍历需要加入集合的元素,循环调用 setTypeAdd 方法将元素加入集合中。
- 标记键被修改,并触发通知事件。
- 变更 server.dirty 值,变更数量为增加的元素数量。
??通过观察sadd方法,我们可以发现集合的底层数据结构也是有两种类型,一种是整数集合,一种是哈希表,并且在每次插入元素的时候,也会判断当前插入的值是否会引起数据结构转型,如果需要转变数据结构则会调用 setTypeConvert 方法转变底层数据结构。
??sadd可以向集合中添加多个元素,得益于使用了遍历的方式来添加元素,每次添加元素都会调用 setTypeAdd 方法。
??和以前代码类型,带有setType前缀的方法,里面也是包含两种数据结构逻辑的方法。
2 sremCommand
2.1 方法说明
??从一个集合中移出元素。
2.2 命令实践
2.3 方法源代码
void sremCommand(redisClient *c) {
robj *set;
int j, deleted = 0, keyremoved = 0;
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
for (j = 2; j < c->argc; j++) {
if (setTypeRemove(set,c->argv[j])) {
deleted++;
if (setTypeSize(set) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
if (deleted) {
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
server.dirty += deleted;
}
addReplyLongLong(c,deleted);
}
2.4 相关源代码
2.4.1 checkType
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
int checkType(redisClient *c, robj *o, int type) {
if (o->type != type) {
addReply(c,shared.wrongtypeerr);
return 1;
}
return 0;
}
2.4.2 setTypeRemove
int setTypeRemove(robj *setobj, robj *value) {
long long llval;
if (setobj->encoding == REDIS_ENCODING_HT) {
if (dictDelete(setobj->ptr,value) == DICT_OK) {
if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
return 1;
}
}
else if (setobj->encoding == REDIS_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
int success;
setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
if (success) return 1;
}
} else {
redisPanic("Unknown set encoding");
}
return 0;
}
2.4.3 setTypeSize
unsigned long setTypeSize(robj *subject) {
if (subject->encoding == REDIS_ENCODING_HT) {
return dictSize((dict*)subject->ptr);
}
else if (subject->encoding == REDIS_ENCODING_INTSET) {
return intsetLen((intset*)subject->ptr);
} else {
redisPanic("Unknown set encoding");
}
}
2.5 代码理解
- 获取键对象,并检查对象类型是否为集合类型。
- 遍历需要移出的元素,循环调用 setTypeRemove 方法移出元素。
- 如果集合的元素个数为0时,则需要删除这个集合,用setTypeSize获取集合长度。
- 标记键修改,并触发通知事件
- 返回被删除元素的个数
??整体的思路其实和 sadd差不多,一个是添加元素,一个是移出元素,都是先拿到集合对象,然后遍历元素在调用相应的方法。
??这次相关源代码中,添加了一个我们经常见到的 checkType ,虽然它定义在object.c文件中,不过先拿出来阅读下,也不是不可以,免得每次遇到它也不知道葫芦里到底卖的什么药,可以看到方法里其实并没有什么逻辑,就是单纯判断当前对象的类型和传入的类型是否相等,不相等的就报错并返回0,相等的话返回1。
??并且可以看到宏定义刚好有5个,是我们熟悉的5个数据类型。
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4
3 总结
- 集合类型有两种底层数据结构,一种是整数集合,一种是哈希表。
- 集合类型再添加元素的时候,会判断是否要转变数据结构。
- 是否要转变数据结构判断标准是,集合长度是否超过配置,集合元素类型是否不为数字。
- sadd 和 srem 都可以同时操作多个元素。
- checkType 方法定义在object.c中,主要逻辑时判断对象的类型和传入的类型是否相等。
|