对象类型与编码方式

  对于字符串类型的命令,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方式编码的对象的内存结构为:

  

redis 特殊字符 redis 字符编码_redis


  对象的robject和具体数据buf都是紧凑相连的,其中robject为16字节,sdshdr为8字节,buf数组的末尾还需要加上1字节的’\0’表示结束。在redis中,要求embstr编码的对象最大为64字节,因此数据部分的长度最多为:64-(16+8+1)=39字节。

  因此当字符串长度不超过39字节时,会采用embstr编码方式,否则会采用raw编码方式。

  

1)int编码方式

  int编码方式用的比较少,当数据为纯数字时,才可能会使用int编码方式

  

redis 特殊字符 redis 字符编码_编码方式_02


  其中ptr直接存储数据值

2)raw编码方式

eg:set num “123456789101112131415161718192021222324252627282930”

  命令:set

  key:url

  value:123456789101112131415161718192021222324252627282930

  由于value的长度非常长(超过了39个字节),因此采用raw编码方式

  

redis 特殊字符 redis 字符编码_redis 特殊字符_03

3)embstr编码方式

eg: set msg “hello”

  value为字符串“hello”,采用embstr编码方式,最后生成的对象为:

  

redis 特殊字符 redis 字符编码_redis 特殊字符_04


  可以发现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 原来对应的值的末尾。

  

redis 特殊字符 redis 字符编码_编码方式_05

  

//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 就覆写旧值,无视类型。

  

redis 特殊字符 redis 字符编码_编码方式_06

  

/* 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/