redis学习笔记(13)---列表命令及实现
来源:互联网 发布:google chrome mac下载 编辑:程序博客网 时间:2024/05/21 11:04
列表命令
Redis中的List对象的类型为REDIS_LIST,是一种双向链表结构,主要支持以下几种命令:
- LPUSH 向列表左端添加元素,用法:
LPUSH key value
- RPUSH 向列表右端添加元素,用法:
RPUSH key value
- LPOP 从列表左端弹出元素,用法:
LPOP key
- RPOP 从列表右端弹出元素,用法:
RPOP key
- LLEN 获取列表中元素个数,用法:
LLEN key
- LRANGE 获取列表中某一片段的元素,用法:
LRANGE key start stop
,index从0开始,-1表示最后一个元素 - LREM 删除列表中指定的值,用法:
LREM key count value
,删除列表中前count个值为value的元素,当count>0时从左边开始数,count<0时从右边开始数,count=0时会删除所有值为value的元素 - LINDEX 获取指定索引的元素值,用法:
LINDEX key index
- LSET 设置指定索引的元素值,用法:
LSET key index value
- LTRIM 只保留列表指定片段,用法:
LTRIM key start stop
,包含start和stop - LINSERT 像列表中插入元素,用法:
LINSERT key BEFORE|AFTER privot value
,从左边开始寻找值为privot的第一个元素,然后根据第二个参数是BEFORE还是AFTER决定在该元素的前面还是后面插入value - RPOPLPUSH 将元素从一个列表转义到另一个列表,用法:
RPOPLPUSH source destination
除此之外,还有一些阻塞式命令:
BLPOP
BRPOP
BRPOPLPUSH
编码方式
List的相关操作主要定义在t_list.c中。
列表对象支持两种编码方式:
#define REDIS_ENCODING_LINKEDLIST 4 #define REDIS_ENCODING_ZIPLIST 5
即底层列表可以通过ziplist或LinkedList来实现,两种链表的实现分别在ziplist.c和adlist.c中 命令的具体实现
在上一章中,我们已经知道当有一个客户端请求到来时
- server端会调用readQueryFromClient()来进行处理
- 其中首先会调用processInputBuffer()来解析输入请求,并为每一个参数创建一个字符串对象
- 调用processCommand并最终调用call来执行命令
1、push命令 (lpush、rpush、lpushx、rpushx)
1.1、lpush & rpush
eg:LPUSH key value [value …]
lpush将一个或多个值 value 插入到列表 key 的表头,rpush则是插入到表的尾部
当 key 存在但不是列表类型时,返回一个错误
如果 key 不存在,一个空列表会被创建并执行 LPUSH 操作
在调用 lpush key val1 val2 val3时, redis是从前到后依次将每一个value插入到list的头部的。
即先将val1插入到头部,再将val2插入到头部,最后将val3插入到头部,因此最后val3会在list的头部。
即最终在list中的先后顺序是:
表头—>val3—>val2—>val1—>表尾
1.2、lpushx & rpushx
eg:LPUSHX key value
lpushx将value 插入到列表 key 的表头, rpushx则插入到表的尾部
当且仅当 key 存在并且是一个列表
和 LPUSH 命令相反,当 key 不存在时, LPUSHX 命令什么也不做
以lpush为例对整个流程进行简单分析
以 lpush zoo tiger为例,在调用call之前,redis已经为这3个参数分别创建了一个字符串对象,示意图如下:
然后在call中会调用lpushCommand()进行处理
1)当key(zoo)在数据库中存在,且不为REDIS_LIST类型时,错误,不能执行
2)从argv[2]开始为每个对象调用tryObjectEncoding
3)当key不存在时,创建一个类型为REDIS_LIST、编码方式为ziplist的对象lobj,并将key-lobj对插入到数据库中。即key仍为字符串类型,但是对应的value为list类型
4)调用listTypePush
依次将每个value插入到链表lobj中
5)调用addReplyLongLong
将操作的结果返回给client
void lpushCommand(redisClient *c) { pushGenericCommand(c,REDIS_HEAD);}void pushGenericCommand(redisClient *c, int where) { robj *lobj = lookupKeyWrite(c->db,c->argv[1]); //argv[1]为zoo if (lobj && lobj->type != REDIS_LIST) { //key存在,但不为list类型,错误 addReply(c,shared.wrongtypeerr); return; } for (j = 2; j < c->argc; j++) { c->argv[j] = tryObjectEncoding(c->argv[j]); if (!lobj) { //key不存在,创建一个list对象lobj lobj = createZiplistObject(); dbAdd(c->db,c->argv[1],lobj); //将key-lobj对插入到数据库中 } listTypePush(lobj,c->argv[j],where); //依次将每个value插入到链表lobj pushed++; } addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); //返回结果给client}
所有的key-value对最终都是插入到c->db中的,因此每个db中的key必须都是唯一的,不管数据的底层实现是哪种类型。
当lobj==NULL时,表明当前数据库中还没有zoo这个key,此时就需要创建一个list类型的对象lobj,并将key-lobj插入到数据库中。
然后在listTypePush()中依次向list中插入每一个value
void listTypePush(robj *subject, robj *value, int where) { /* 判断是否需要将ziplist转换为LinkedList */ listTypeTryConversion(subject,value); if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) >= server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); // 将value加入到subject中 if (subject->encoding == REDIS_ENCODING_ZIPLIST) { int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL; value = getDecodedObject(value); subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos); } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) { if (where == REDIS_HEAD) { listAddNodeHead(subject->ptr,value); } else { listAddNodeTail(subject->ptr,value); } }}
默认情况下List使用ziplist编码 ,当满足下面两个条件之一时会转变为LinkedList编码:
- 当待添加的新字符串长度 超过server.list_max_ziplist_value (默认为64)时
- ziplist中的节点数量 超过server.list_max_ziplist_entries(默认为512)时
可以发现:
1)当lobj为ziplist时,会调用ziplistPush()
2)当lobj为LinkedList时,会调用listAddNodeHead()或listAddNodeTail()
这样就将每一个value插入到list中了。
2、pop命令(lpop、rpop)
LPOP key 移除并返回列表 key 的头元素,当 key 不存在时,返回 nil RPOP则相反,返回的是列表的尾部元素以lpop为例进行分析 首先在数据库中查找key对应的列表,当列表不存在或key对应的value不为list类型时,直接返回 否则调用listTypePop从key对应的链表中弹出元素value 将操作结果返回给client
void lpopCommand(redisClient *c) { popGenericCommand(c,REDIS_HEAD); //对头部元素进行处理} void popGenericCommand(redisClient *c, int where) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; robj *value = listTypePop(o,where); if (value == NULL) { addReply(c,shared.nullbulk); } else { char *event = (where == REDIS_HEAD) ? "lpop" : "rpop"; addReplyBulk(c,value); }}
3、linsert
LINSERT key BEFORE|AFTER pivot value
将value插入到key列表中元素pivot的before或after
首先根据before、after来判断是插入到头部还是尾部
然后调用pushxGenericCommand完成插入操作
void linsertCommand(redisClient *c) { c->argv[4] = tryObjectEncoding(c->argv[4]); if (strcasecmp(c->argv[2]->ptr,"after") == 0) { pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL); } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) { pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_HEAD); } else { addReply(c,shared.syntaxerr); }}
在pushxGenericCommand中
1)首先在db中查找key对应的列表,当列表不存在或元素类型不为list时,直接返回
2)当相对元素pivot不为空时
a)首先找到元素pivot对应的位置
b)若能找到,则调用listTypeInsert插入元素
c)返回操作结果
3)若pivot为NULL时,直接调用listTypePush完成插入操作,并返回结果
void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { if ((subject = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,subject,REDIS_LIST)) return; //1)查找key if (refval != NULL) { //2)pivot不为空时 listTypeTryConversion(subject,val); iter = listTypeInitIterator(subject,0,REDIS_TAIL); while (listTypeNext(iter,&entry)) { if (listTypeEqual(&entry,refval)) {//2.a)找到pivot元素的位置 listTypeInsert(&entry,val,where); //2.b)插入value inserted = 1; //插入完成 break; } } if (inserted) { //2.c)返回操作成功的结果 if (subject->encoding == REDIS_ENCODING_ZIPLIST && ziplistLen(subject->ptr) > server.list_max_ziplist_entries) listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST); } else {//2.c)返回操作失败的结果 addReply(c,shared.cnegone); return; } } else { //3)插入元素 listTypePush(subject,val,where); } addReplyLongLong(c,listTypeLength(subject)); //返回操作结果}
4、llen
LLEN key
返回列表key的长度
首先在数据库中查找key对应的列表,当列表不存在或key对应的value不为list类型时,直接返回
否则调用listTypeLength()获取列表长度
并将结果返回给client
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));}
5、lindex
LINDEX key index
返回列表key中第index个元素,从0开始计数
index可以为负值
void lindexCommand(redisClient *c) { robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK)) return; if (o->encoding == REDIS_ENCODING_ZIPLIST) { //ziplist 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); } else { addReply(c,shared.nullbulk); } } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { //LinkedList listNode *ln = listIndex(o->ptr,index); if (ln != NULL) { value = listNodeValue(ln); addReplyBulk(c,value); } else { addReply(c,shared.nullbulk); } } }
总结
对于列表类型的插入、弹出及其他等命令,一般的执行过程都是:
1)在db中查找key对应的列表,可以分为列表不存在,元素存在但不为list、列表存在这三种情况
2)然后根据三种情况,对命令继续处理,包括直接返回或创建一个新链表等
3)在列表存在时,对list执行push、pop、insert、length等操作
4)将操作的结果返回给client
之前已经对字符串命令的实现进行了分析,除了字符串、列表之外,还有哈希表、集合、有序集合这几种底层实现。
这几种类型对应的命令实现方式与字符串、列表基本类似,其实现分别在t_hash.c 、t_set.c 、t_zset.c这几个文件中。
server收到客户端的请求后,都会在call函数中执行这些命令。
本文所引用的源码全部来自Redis3.0.7版本
- redis学习笔记(13)---列表命令及实现
- redis学习笔记(11)---字符串命令及实现
- Redis 学习笔记(一) — 环境搭建及命令
- Redis命令学习—List(列表)
- Redis学习笔记(九)redis实现时时直播列表缓存,支持分页[热点数据存储]
- redis学习笔记三之基础命令—列表
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- [Redis学习笔记]-Redis命令
- redis命令,学习笔记
- redis命令学习笔记
- redis学习笔记三(列表)
- Redis学习(一)-基础理论、字符串命令、列表结构
- redis列表(list)命令
- redis学习笔记(14)---redis基本命令总结
- Redis 学习笔记(十)Redis sort 排序命令详解
- 网络安全
- DispatchAction
- 周总结
- java 汉诺塔问题
- 数据分析/数据挖掘/机器学习---- 必读书目
- redis学习笔记(13)---列表命令及实现
- UNIX环境高级编程(阅读笔记)---多线程信号
- 大端模式和小端模式
- 响应式web设计之CSS3 Media Queries
- JSON解析-系统方法和第三方JSONKit的简单实用
- CTSC&&APIO2016 旅游记&&被坑记
- Hive分析窗口函数(一) SUM,AVG,MIN,MAX
- 工业设备IP等级
- dracut using