redis当中集合对象的底层实现为intset和hashtable实现,用hashtable实现时,存储具体值的是key,value统一用NULL。其实集合对象的实现和hash对象的实现还是非常类似的,都是尽可能用占用空间小的底层类型存储,如果实在存不下了,就得鸟枪换炮了
老规矩,还是先说转换的条件,由于占地较小的实现为intset,这就导致发生转化的条件比
zipmap->hashtable要不一样了,但也是一共两项,若有一项或一项以上没法满足,则intset转为hashtable:
1. 集合对象均为整数值;
2. intset中的元素个数超过512个
其中第二个限制可以在redis.conf文件中修改
# set in order to use this special memory saving encoding. set-max-intset-entries 512
其转换代码如下:
void setTypeConvert(robj *setobj, int enc) { 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)); /* 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"); } }
其中用到转换代码的函数如下所示:
int setTypeAdd(robj *subject, sds value) { long long llval; if (subject->encoding == OBJ_ENCODING_HT) {/*如果是hashtable,直接加*/ dict *ht = subject->ptr; dictEntry *de = dictAddRaw(ht,value,NULL); if (de) { dictSetKey(ht,de,sdsdup(value)); dictSetVal(ht,de,NULL); return 1; } } else if (subject->encoding == OBJ_ENCODING_INTSET) { if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {//先判断是否为整数*/ uint8_t success = 0; subject->ptr = intsetAdd(subject->ptr,llval,&success);/*插入是否成功*/ 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 {/*转换类型*/ /* 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; }
至于其留给客户端的命令接口,这里也只列出一个,其余不再赘述:
void saddCommand(client *c) { robj *set; int j, added = 0; set = lookupKeyWrite(c->db,c->argv[1]);/*在db中查找指定key*/ if (set == NULL) {/*为空则创建*/ set = setTypeCreate(c->argv[2]->ptr); dbAdd(c->db,c->argv[1],set); } else {/*不为空则插入*/ if (set->type != OBJ_SET) {/*判断*/ addReply(c,shared.wrongtypeerr); return; } } for (j = 2; j < c->argc; j++) { if (setTypeAdd(set,c->argv[j]->ptr)) added++; } if (added) { signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id); } server.dirty += added; addReplyLongLong(c,added); }