redis学习笔记(11)---字符串命令及实现
来源:互联网 发布:h3c路由配置端口ip 编辑:程序博客网 时间:2024/05/16 15:59
对象类型与编码方式
对于字符串类型的命令,redis数据库会为每个对象创建一个字符串类型(REDIS_STRING)的对象。
对于字符串类型的对象,可以支持三种编码方式:
#define REDIS_ENCODING_RAW 0 /* Raw representation */#define REDIS_ENCODING_INT 1 /* Encoded as integer */#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
只有对于纯数字,才会将字符串编码为int,如执行BITCOUNT命令时。
由于一个embstr方式编码的对象的内存结构为:
对象的robject和具体数据buf都是紧凑相连的,其中robject为16字节,sdshdr为8字节,buf数组的末尾还需要加上1字节的’\0’表示结束。在redis中,要求embstr编码的对象最大为64字节,因此数据部分的长度最多为:64-(16+8+1)=39字节。
因此当字符串长度不超过39字节时,会采用embstr编码方式,否则会采用raw编码方式。
1)int编码方式
int编码方式用的比较少,当数据为纯数字时,才可能会使用int编码方式
其中ptr直接存储数据值
2)raw编码方式
eg:set num “123456789101112131415161718192021222324252627282930”
命令:set
key:url
value:123456789101112131415161718192021222324252627282930
由于value的长度非常长(超过了39个字节),因此采用raw编码方式
3)embstr编码方式
eg: set msg “hello”
value为字符串“hello”,采用embstr编码方式,最后生成的对象为:
可以发现robject、sds在内存空间中是连续的
命令:
位操作命令(bitops.c)
- GETBIT 获取一个键值的二进制位的指定位置的值(0/1),用法:
GETBIT key offset
- SETBIT 设置一个键值的二进制位的指定位置的值(0/1),用法:
SETBIT key offset value
- BITCOUNT 获取一个键值的一个范围内的二进制表示的1的个数,用法:
BITCOUNT key [start end]
- BITOP 该命令可以对多个字符串类型键进行位运算,并将结果存储到指定的键中,BITOP支持的运算包含:OR,AND,XOR,NOT,用法:
BITOP OP desKey key1 key2
- BITPOS 获取指定键的第一个位值为0或者1的位置,用法:
BITPOS key 0/1 [start, end]
1)SETBIT
SETBIT key offset value
将key的第offset位设置为value,因此value只能为0或1,默认为0。
注意offset是以bit位为单位的,且offset 参数必须在[0,2^32)之间。 (key的长度被限制在 512 MB 之内)。
/* SETBIT key offset bitvalue */void setbitCommand(redisClient *c) { ///解析offset和value,并检查是否合法 if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK) return; if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK) return; if (on & ~1) { //value只能为0或1,否则错误 addReplyError(c,err); return; } o = lookupKeyWrite(c->db,c->argv[1]); //在数据库中查找key if (o == NULL) { //当key不存在时,将key插入到数据库中,此时value为null o = createObject(REDIS_STRING,sdsempty()); dbAdd(c->db,c->argv[1],o); } else { //当key不为字符串时,直接返回,否则获得key对应的value if (checkType(c,o,REDIS_STRING)) return; o = dbUnshareStringValue(c->db,c->argv[1],o); } byte = bitoffset >> 3; //offset位对应的字节数 o->ptr = sdsgrowzero(o->ptr,byte+1); //扩展value的空间 byteval = ((uint8_t*)o->ptr)[byte]; bit = 7 - (bitoffset & 0x7); bitval = byteval & (1 << bit); //得到对应bit位原来的值 /* 更新该bit位,并将该bit位原来的值返回给客户端 */ byteval &= ~(1 << bit); //先讲该位清零 byteval |= ((on & 0x1) << bit); //再设置该位 ((uint8_t*)o->ptr)[byte] = byteval;}
2)GETBIT
GETBIT key offset
获取key中第offset位的值,key中bit位是从左向右递增的,即最左为第0位。
当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
同样offset 参数必须在[0,2^32)之间
/* GETBIT key offset */void getbitCommand(redisClient *c) { //解析offset,并检查是否合法 if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK) return; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_STRING)) return; //当key不存在或key不为字符串时,直接返回 byte = bitoffset >> 3; //offset位所在的字节 bit = 7 - (bitoffset & 0x7); //offset在byte字节的第bit位 if (sdsEncodedObject(o)) { //得到第offset位的值 if (byte < sdslen(o->ptr)) bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit); } else { if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr)) bitval = llbuf[byte] & (1 << bit); }}
3)BITOP
BITOP operation destkey key [key …]
对一个或多个保存二进制位的字符串 key 进行位操作,并将结果保存到 destkey
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种,NOT只接受一个key
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */void bitopCommand(redisClient *c) { /* 解析位运算命令 */ if ((opname[0] == 'a' || opname[0] == 'A') && !strcasecmp(opname,"and")) op = BITOP_AND; else if((opname[0] == 'o' || opname[0] == 'O') && !strcasecmp(opname,"or")) op = BITOP_OR; else if((opname[0] == 'x' || opname[0] == 'X') && !strcasecmp(opname,"xor")) op = BITOP_XOR; else if((opname[0] == 'n' || opname[0] == 'N') && !strcasecmp(opname,"not")) op = BITOP_NOT; else { addReply(c,shared.syntaxerr); return; } if (op == BITOP_NOT && c->argc != 4) { /* NOT操作只接受一个key */ addReplyError(c,"BITOP NOT must be called with a single source key."); return; } for (j = 0; j < numkeys; j++) { o = lookupKeyRead(c->db,c->argv[j+3]); if (o == NULL) { //key不存在时,当做空字符串处理 } if (checkType(c,o,REDIS_STRING)) { //key不为字符串时,返回 return; } } if (maxlen) { if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) { /* 依次对每个key执行位运算 */ if (op == BITOP_AND) { ...... } else if (op == BITOP_OR) { } else if (op == BITOP_XOR) { } else if (op == BITOP_NOT) { } } for (; j < maxlen; j++) { output = (len[0] <= j) ? 0 : src[0][j]; if (op == BITOP_NOT) output = ~output; for (i = 1; i < numkeys; i++) { byte = (len[i] <= j) ? 0 : src[i][j]; switch(op) { case BITOP_AND: output &= byte; break; case BITOP_OR: output |= byte; break; case BITOP_XOR: output ^= byte; break; } } res[j] = output; } }}
4)BITCOUNT
BITCOUNT key [start] [end]
计算第[start,end]个字节中被设置为 1 的比特位的数量。
其中start和end可以为负数,如 -1 表示最后一位,而 -2 表示倒数第二位
注意第end个字节也在计算范围内,且BITCOUNT 是对字节进行计数的。setbit函数是以bit为单位的。
/* BITCOUNT key [start end] */void bitcountCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||checkType(c,o,REDIS_STRING)) return; //当key不存在,或key为字符串时,直接返回 //将key转换为字符串形式 if (o->encoding == REDIS_ENCODING_INT) { p = (unsigned char*) llbuf; strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); } else { p = (unsigned char*) o->ptr; strlen = sdslen(o->ptr); } /* 如果参数个数为4时,解析start、end参数 */ if (c->argc == 4) { // if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK) return; if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK) return; /* 当start、end为负值时,转换为正值 */ if (start < 0) start = strlen+start; if (end < 0) end = strlen+end; if (start < 0) start = 0; if (end < 0) end = 0; if (end >= strlen) end = strlen-1; } else if (c->argc == 2) { //否则就是对整个字符串进行计数 start = 0; end = strlen-1; } else { //其它参数个数为错误 addReply(c,shared.syntaxerr); return; } if (start > end) { //start>end时,结果为0 addReply(c,shared.czero); } else { //否则计算从p+start起的bytes个字节中bit为1的个数 long bytes = end-start+1; addReplyLongLong(c,redisPopcount(p+start,bytes)); }}
5)BITPOS
BITPOS key bit [start [end]]
返回key中第[start,end]个字节中,第一个bit位为bit的位。
同样start、end参数可以为负值
/* BITPOS key bit [start [end]] */void bitposCommand(redisClient *c) { //解析参数bit,并检查合法性 if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK) return; if (bit != 0 && bit != 1) { addReplyError(c, "The bit argument must be 1 or 0."); return; } //当key不存在时,认为key是一串无限长的0,当查找0时,返回第0位,当查找1时,返回-1; if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) { addReplyLongLong(c, bit ? -1 : 0); return; } if (checkType(c,o,REDIS_STRING)) return; //获取key if (o->encoding == REDIS_ENCODING_INT) { p = (unsigned char*) llbuf; strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr); } else { p = (unsigned char*) o->ptr; strlen = sdslen(o->ptr); } /* 解析start、end参数 */ if (c->argc == 4 || c->argc == 5) { ...... } else if (c->argc == 3) { /* The whole string. */ start = 0; end = strlen-1; } else { /* Syntax error. */ addReply(c,shared.syntaxerr); return; } if (start > end) { addReplyLongLong(c, -1); } else { long bytes = end-start+1; long pos = redisBitpos(p+start,bytes,bit); if (end_given && bit == 0 && pos == bytes*8) { addReplyLongLong(c,-1); return; } if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */ addReplyLongLong(c,pos); }}
字符串命令(t_string.c)
- SET 赋值,用法:
SET key value
- GET 取值,用法:
GET key
- INCR 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法:
INCR key
- INCRBY 增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:
INCRBY key increment
,意思是key自增increment,increment可以为负数,表示减少。 - DECR 递减数字,仅仅对数字类型的键有用,相当于Java的i–,用法:
DECR key
- DECRBY 减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:
DECRBY key decrement
,意思是key自减decrement,decrement可以为正数,表示增加。 - INCRBYFLOAT 增加指定浮点数,仅仅对数字类型的键有用,用法:
INCRBYFLOAT key increment
- APPEND 向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:
APPEND key value
- STRLEN 获取字符串长度,用法:
STRLEN key
- MSET 同时设置多个key的值,用法:
MSET key1 value1 [key2 value2 ...]
- MGET 同时获取多个key的值,用法:
MGET key1 [key2 ...]
由于字符串命令非常多,因此只以其中几个为例进行简单说明
1)APPEND
APPEND key value
若 key 不存在, 则将给定 key 设为 value ,就像执行 SET key value 一样。
若 key 已经存在并且是一个字符串, 则将 value 追加到 key 原来对应的值的末尾。
//APPEND key value void appendCommand(redisClient *c) { robj *o, *append; o = lookupKeyWrite(c->db,c->argv[1]); if (o == NULL) { //1)key不存在时,向数据库中插入key-value对 c->argv[2] = tryObjectEncoding(c->argv[2]); dbAdd(c->db,c->argv[1],c->argv[2]); } else { //2)key存在时 if (checkType(c,o,REDIS_STRING)) //2.1)key不为字符串时,直接返回 return; o = dbUnshareStringValue(c->db,c->argv[1],o); o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); //2.2)key为字符串时,将value追加到key原来对应的值的末尾 }}
2)SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]
将字符串值 value 关联到 key 。
如果 key 已经持有其他值, SET 就覆写旧值,无视类型。
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */void setCommand(redisClient *c) { for (j = 3; j < c->argc; j++) { //首先对参数进行解析 char *a = c->argv[j]->ptr; robj *next = (j == c->argc-1) ? NULL : c->argv[j+1]; if ((a[0] == 'n' || a[0] == 'N') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') { flags |= REDIS_SET_NX; } else if ((a[0] == 'x' || a[0] == 'X') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0') { flags |= REDIS_SET_XX; } else if ((a[0] == 'e' || a[0] == 'E') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) { unit = UNIT_SECONDS; expire = next; j++; } else if ((a[0] == 'p' || a[0] == 'P') && (a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) { unit = UNIT_MILLISECONDS; expire = next; j++; } else { addReply(c,shared.syntaxerr); return; } } c->argv[2] = tryObjectEncoding(c->argv[2]); setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL); //真正操作} //真正执行set命令的函数void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) { long long milliseconds = 0; /* initialized to avoid any harmness warning */ if (expire) { //当有超时时间时,计算超时时间 if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK) return; if (milliseconds <= 0) { addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); return; } if (unit == UNIT_SECONDS) milliseconds *= 1000; } if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) || (flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL)) { //参数不合法时,直接返回 addReply(c, abort_reply ? abort_reply : shared.nullbulk); return; } setKey(c->db,key,val); //将key-val对插入到数据库中 if (expire) setExpire(c->db,key,mstime()+milliseconds); //设置超时时间}
本文所引用的源码全部来自Redis3.0.7版本
redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)
http://doc.redisfans.com/
http://blog.csdn.net/hechurui/article/details/49508735
- redis学习笔记(11)---字符串命令及实现
- redis学习笔记(13)---列表命令及实现
- Redis 学习笔记(二)之字符串类型命令
- Redis 学习笔记(二)之字符串类型命令
- Redis 学习笔记(一) — 环境搭建及命令
- Redis学习笔记(三)——Redis常用命令入门——字符串类型命令
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- Redis学习笔记2--Redis数据类型及相关命令
- [Redis学习笔记]-Redis命令
- redis命令,学习笔记
- redis命令学习笔记
- redis学习笔记(1)---字符串sds
- redis 字符串命令学习1
- redis学习笔记(14)---redis基本命令总结
- Redis 学习笔记(十)Redis sort 排序命令详解
- Redis学习笔记---字符串类型
- mdadm做软raid
- Spring-JDBC通用Dao
- Java 本地方法
- 表单选择器
- 设计模式 策略模式 以角色游戏为背景
- redis学习笔记(11)---字符串命令及实现
- Ajax Post数据时加号变空格的解决办法
- 检测判断
- android support 版本相关
- 关于http代理
- 数据库
- DOM 基本操作
- 事件处理
- 独步spark