redis学习笔记(11)---字符串命令及实现

对象类型与编码方式  

  对于字符串类型的命令,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

时间: 2024-10-18 00:46:01

redis学习笔记(11)---字符串命令及实现的相关文章

Redis学习笔记4-Redis配置详解

原文:  http://blog.csdn.net/mashangyou/article/details/24555191 在Redis中直接启动redis-server服务时, 采用的是默认的配置文件.采用redis-server   xxx.conf 这样的方式可以按照指定的配置文件来运行Redis服务.按照本Redis学习笔记中Redis的按照方式按照后,Redis的配置文件是/etc/redis/6379.conf.下面是Redis2.8.9的配置文件各项的中文解释. 1 #daemon

Redis学习笔记

Redis学习笔记:Redis是什么?redis是开源BSD许可高级的key-vlue存储系统可以用来存储字符串哈希结构链表.结构.集合,因此常用来提供数据结构服务. redis和memcache相比的独特之处:1.redis可以用来做存储,而memcache是用来做缓存 这个特点主要因为其有"持久化"的功能.2.存储的数据有"结构",对于memcache来说,存储的数据只有1种类型"字符串"而 redis则可以存储字符串.链表.哈希机构.集合.

(转)redis 学习笔记(1)-编译、启动、停止

redis 学习笔记(1)-编译.启动.停止 一.下载.编译 redis是以源码方式发行的,先下载源码,然后在linux下编译 1.1 http://www.redis.io/download 先到这里下载Stable稳定版,目前最新版本是2.8.17 1.2 上传到linux,然后运行以下命令解压 tar xzf redis-2.8.17.tar.gz 1.3 编译 cd redis-2.8.17make 注:make命令需要linux上安装gcc,若机器上未安装gcc,redhat环境下,如

《C++ Primer Plus》学习笔记11

<C++ Primer Plus>学习笔记11 第17章 输入.输出和文件 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

Redis学习笔记4-Redis配置具体解释

在Redis中直接启动redis-server服务时, 採用的是默认的配置文件.採用redis-server   xxx.conf 这种方式能够依照指定的配置文件来执行Redis服务. 依照本Redis学习笔记中Redis的依照方式依照后,Redis的配置文件是/etc/redis/6379.conf.以下是Redis2.8.9的配置文件各项的中文解释. #daemonize no 默认情况下, redis 不是在后台运行的.假设须要在后台运行,把该项的值更改为 yes daemonize ye

Redis学习笔记(简单了解与运行)

Redis学习笔记(简单了解与运行) 开源的非关系型数据库 是REmote Dictionary Server(远程字典服务器)的缩写,以字典结构存储数据 允许其他应用通过TCP协议读写字典中的内容. Redis支持存储的键值数据类型 字符串类型 散列类型 列表类型 集合类型 有序集合类型 Redis的特性 通过一个列子看出Mysql和Redis的存储区别 例如: (存储一篇文章,文章包括:标题(title),正文(content),阅读量(views),标签(tags)) 需求: 把数据存储在

python 学习笔记 11 -- 使用参数使你的程序变得更性感

当然,在之前的系列中,我已介绍如何给 Python 脚本传参,当然,今天不会继续介绍这么无聊的东东.首先使用 python 的sys.argv 传参的话,就固定了参数的个数.顺序以及格式,这么死的规定如何性感? I have a dream , to make my code much sexer ! 今天我们简单介绍一下如何更加随性的给 python 脚本传参.效果如下: [email protected]:/tmp$ python arg.py -h NAME: project with u

Redis学习笔记(增删查)

Redis学习笔记(增删查) 向数据库中添加一个键 SET key value 获取数据库中的key KEYS pattern pattern支持glob风格通配符格式 " ? " 匹配一个字符 " * " 匹配任意字符 " [] " 匹配括号间的任一字符,可以使用" - "符号表示一个范围,例如:a[a-z]c " \x " 匹配字符x,用于转义字符.如需要匹配"?",就需要用 \?

Swift学习笔记(4)--字符串及基本使用

String是例如"hello, world","海贼王" 这样的有序的Character(字符)类型的值的集合,通过String类型来表示. Swift 的String类型与 Foundation NSString类进行了无缝桥接.如果您利用 Cocoa 或 Cocoa Touch 中的 Foundation 框架进行工作.所有NSString API 都可以调用您创建的任意String类型的值.除此之外,还可以使用本章介绍的String特性.您也可以在任意要求传

Redis学习笔记~目录

redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集合)和hashs(哈希类型).这些数据类型都 支持push/pop.add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的.在此基础上,redis支持各种不同方式的排 序.与memcached一样,为了保证效率,数据都是缓存在内存中.区别的是redis会周期性的把更