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)  

  1. GETBIT 获取一个键值的二进制位的指定位置的值(0/1),用法:GETBIT key offset
  2. SETBIT 设置一个键值的二进制位的指定位置的值(0/1),用法:SETBIT key offset value
  3. BITCOUNT 获取一个键值的一个范围内的二进制表示的1的个数,用法:BITCOUNT key [start end]
  4. BITOP 该命令可以对多个字符串类型键进行位运算,并将结果存储到指定的键中,BITOP支持的运算包含:OR,AND,XOR,NOT,用法:BITOP OP desKey key1 key2
  5. 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)  

  1. SET 赋值,用法: SET key value
  2. GET 取值,用法: GET key
  3. INCR 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法: INCR key
  4. INCRBY 增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:INCRBY key increment,意思是key自增increment,increment可以为负数,表示减少。
  5. DECR 递减数字,仅仅对数字类型的键有用,相当于Java的i–,用法:DECR key
  6. DECRBY 减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:DECRBY key decrement,意思是key自减decrement,decrement可以为正数,表示增加。
  7. INCRBYFLOAT 增加指定浮点数,仅仅对数字类型的键有用,用法:INCRBYFLOAT key increment
  8. APPEND 向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:APPEND key value
  9. STRLEN 获取字符串长度,用法:STRLEN key
  10. MSET 同时设置多个key的值,用法:MSET key1 value1 [key2 value2 ...]
  11. 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

0 0
原创粉丝点击