下面我看看集合对象,内部编码也有两种:
intset
hashtable
复制代码
同样的问题,分别什么条件用哪种编码呢?
当集合中元素都是整数且元素个数小于set-max-intset-entries(512),用intset作为内部实现,减少内存的使用,反之hashtable。
复制代码
看看set有哪些常用的命令呢?
sadd:saddCommand
srem
scard
sismember
srandmember
spop
smembers
复制代码
源码实现在t_set.c中
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);
}
robj *setTypeCreate(robj *value) {
if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
return createIntsetObject();
return createSetObject();
}
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;
}
在intset.h和intset.c中
typedef struct intset {
uint32_t encoding;//编码方式,int16_t、int32_t、int64_t
uint32_t length;//集合包含元素的数量
int8_t contents[];//保存元素的数组
} intset;
intset相对比较简单,就暂时解读到这。
接下来看看有序集合zset的实现,根据名字就知道他也是一个集合,那么他是如何实现排序的呢?
先看看几个常用的命令:
zadd
zcard
zscore
zrank/zrevrank
zrange/zrevrange
zset有两种编码
ziplist:zset的元素个数小于zset-max-ziplist-entries(128),同时每个元素的值小于zset-max-ziplist-value(64字节)
skiplist:反之
到这,我们发现,redis默认的内部编码都是为了节约内存考虑的。
先来看看跳跃表这种数据结构的实现,在redis.h中
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length; //跳跃表的长度
int level; //层数最大的那个节点的层数
} zskiplist;
typedef struct zskiplistNode {
robj *obj;
double score;
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
unsigned int span; //跨度,用来计算排位rank的
} level[];
} zskiplistNode;
节点按score从小到大排序,分值相同按照成员对象obj在字典序中的大小排序
层数如何确定?
每次创建一个新跳跃表节点的时候,程序都根据幂次定律随机生成一个介于1和32之间的值作为数组的大小。
#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
int zslRandomLevel(void) {
int level = 1;
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
level += 1;
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}复制代码