1 llenCommand
1.1 方法说明
??获取一个列表元素的个数
1.2 命令实践
1.3 方法源代码
void llenCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
addReplyLongLong(c,listTypeLength(o));
}
1.4 代码理解
- 先获取键的对象
- 检查对象类型是否为列表类型
- 调用 listTypeLength这个方法获取列表元素的个数
2 listTypeLength
2.1 方法说明
??可以获取列表建的元素个数,在多处被调用。
2.2 方法源代码
unsigned long listTypeLength(robj *subject) {
if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
return ziplistLen(subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
return listLength((list*)subject->ptr);
} else {
redisPanic("Unknown list encoding");
}
}
2.3 代码理解
- 判断列表数据结构类型。
- 如果是压缩表,则调用压缩表获取长度的方法。
- 如果是链表,则调用链表获取长度的方法。
3 lindexCommand
3.1 方法说明
??获取列表某个位置的元素,成功返回元素的值。
3.2 命令实践
3.2 方法源代码
void lindexCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
long index;
robj *value = NULL;
if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK))
return;
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p;
unsigned char *vstr;
unsigned int vlen;
long long vlong;
p = ziplistIndex(o->ptr,index);
if (ziplistGet(p,&vstr,&vlen,&vlong)) {
if (vstr) {
value = createStringObject((char*)vstr,vlen);
} else {
value = createStringObjectFromLongLong(vlong);
}
addReplyBulk(c,value);
decrRefCount(value);
} else {
addReply(c,shared.nullbulk);
}
}
else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln = listIndex(o->ptr,index);
if (ln != NULL) {
value = listNodeValue(ln);
addReplyBulk(c,value);
} else {
addReply(c,shared.nullbulk);
}
} else {
redisPanic("Unknown list encoding");
}
}
3.4 代码理解
- 先获取键的对象,并判断键对象类型是否为列表。
- 获取索引位置参数。
- 判断数据结构类型。
- 如果是压缩表,则调用压缩表相关方法,获取指定位置的元素。
- 如果是链表,则调用链表相关方法,获取指定位置的元素。
4 lrangeCommand
4.1 方法说明
??获取指定索引位置到另一个位置之间的所有元素。
4.2 命令实践
4.3 方法源代码
void lrangeCommand(redisClient *c) {
robj *o;
long start, end, llen, rangelen;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_LIST)) return;
llen = listTypeLength(o);
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
if (start > end || start >= llen) {
addReply(c,shared.emptymultibulk);
return;
}
if (end >= llen) end = llen-1;
rangelen = (end-start)+1;
addReplyMultiBulkLen(c,rangelen);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
unsigned char *p = ziplistIndex(o->ptr,start);
unsigned char *vstr;
unsigned int vlen;
long long vlong;
while(rangelen--) {
ziplistGet(p,&vstr,&vlen,&vlong);
if (vstr) {
addReplyBulkCBuffer(c,vstr,vlen);
} else {
addReplyBulkLongLong(c,vlong);
}
p = ziplistNext(o->ptr,p);
}
}
else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNode *ln;
if (start > llen/2) start -= llen;
ln = listIndex(o->ptr,start);
while(rangelen--) {
addReplyBulk(c,ln->value);
ln = ln->next;
}
} else {
redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
}
}
4.4 代码理解
??先看下这个方法大致流程。
- 先定义开始位置、结束位置、列表长度、遍历长度等几个关键变量。
- 获取开始位置、结束位置的值,并判断值的是否为数字。
- 获取键对象,并判断键对象的类型是否列表键。
- 获取列表的长度。
- 判断遍历位置是否为负数,如果为负数则转换为相对应的正数。
- 校验起始位置和结束位置是否合理。
- 计算遍历元素的个数。
- 判断列表的数据结构。
- 如果是压缩表,则调用压缩表相关遍历方法,从开始位置遍历。
- 如果是链表,则调用链表相关遍历方法,从开始位置遍历。
??可以看到这个方法不同以往,没有把核心逻辑单独放到某个listType方法里,而是直接写在command方法里,说明没有其他地方调用这一大段逻辑,并且里面大部分代码和 lindex 方法逻辑大同小异,只不过是从拿一个元素变成了拿连续的多个元素,并且这里的负数转换的逻辑和t_string.c里的getrangeCommand基本相似,可见思路是一样的。
??方法里出现了大量关于位置的代码,我们可以学习到如何处理从一个数组中截取一段位置的元素,并且在遍历列表的时候,考虑到了链表查找某个元素的特性,做了一点小优化都是我们值得思考的,从这里也能看出来使用这个方法时间复杂度还是挺高的。
5 总结
- 这节我们总共学习了llen、lindex、lrange三个命令。
- listType 前缀的方法都是对核心逻辑的封装方法,表示会被多处调用,例如listTypeLength。
- 如果不需要多处调用,则会直接把核心逻辑写在Command方法里。
- 链表结构查找一个元素比较麻烦。
- 客户端输入的命令放在 c->argv 这个参数里,例如c->argv[1] 、c->argv[2] 。
|