zoukankan      html  css  js  c++  java
  • Redis系列(四):数据结构String类型中基本操作命令和源码解析

    1.介绍

    string类型本质上是char[]数组的封装 

    中文网:http://www.redis.cn/commands.html#string 

    2.常用命令

    set /get

    set命令的时间复杂度是O(1)

    将键key设定为指定的“字符串”值。

    如果 key 已经保存了一个值,那么这个操作会直接覆盖原来的值,并且忽略原始类型。

    set命令执行成功之后,之前设置的过期时间都将失效

    SET key value [EX seconds] [PX milliseconds] [NX|XX]

    EX seconds – 设置键key的过期时间,单位时秒

    PX milliseconds – 设置键key的过期时间,单位时毫秒

    NX – 只有键key不存在的时候才会设置key的值  

    XX – 只有键key存在的时候才会设置key的值

    127.0.0.1:6379> set myKey "Hello"
    OK
    127.0.0.1:6379> get myKey
    "Hello"
    127.0.0.1:6379> set userId "1"
    OK
    127.0.0.1:6379> get userId
    "1"
    127.0.0.1:6379> object encoding userId
    "int"
    127.0.0.1:6379> object encoding myKey
    "embstr"
    127.0.0.1:6379> set myKey World NX
    (nil)
    127.0.0.1:6379> set myKey World XX
    OK
    127.0.0.1:6379>

    NX :应用场景分布式锁:通过myKey的赋值来判断是否获取到了一个分布式锁  如果OK说明获取到了锁 如果nil说明没有获取到了锁

    如果存放到string中的value是int,那么在内部还是int ,可以从encoding

    redisObject中有一个type属性和encoding属性

    源码解析

    redisCommand存放着所有的命令

        {"set",setCommand,-3,
         "write use-memory @string",
         0,NULL,1,1,1,0,0,0},

     setCommand源码

    setCommand传入一个client结构体 

    最后调用setGenericCommand函数来处理set

    /* SET key value [NX] [XX] [KEEPTTL] [EX <seconds>] [PX <milliseconds>] */
    void setCommand(client *c) {
        int j;
        robj *expire = NULL;
        int unit = UNIT_SECONDS;
        int flags = OBJ_SET_NO_FLAGS;
    
        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] == '' &&
                !(flags & OBJ_SET_XX))
            {
                flags |= OBJ_SET_NX;
            } else if ((a[0] == 'x' || a[0] == 'X') &&
                       (a[1] == 'x' || a[1] == 'X') && a[2] == '' &&
                       !(flags & OBJ_SET_NX))
            {
                flags |= OBJ_SET_XX;
            } else if (!strcasecmp(c->argv[j]->ptr,"KEEPTTL") &&
                       !(flags & OBJ_SET_EX) && !(flags & OBJ_SET_PX))
            {
                flags |= OBJ_SET_KEEPTTL;
            } else if ((a[0] == 'e' || a[0] == 'E') &&
                       (a[1] == 'x' || a[1] == 'X') && a[2] == '' &&
                       !(flags & OBJ_SET_KEEPTTL) &&
                       !(flags & OBJ_SET_PX) && next)
            {
                flags |= OBJ_SET_EX;
                unit = UNIT_SECONDS;
                expire = next;
                j++;
            } else if ((a[0] == 'p' || a[0] == 'P') &&
                       (a[1] == 'x' || a[1] == 'X') && a[2] == '' &&
                       !(flags & OBJ_SET_KEEPTTL) &&
                       !(flags & OBJ_SET_EX) && next)
            {
                flags |= OBJ_SET_PX;
                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);
    }

     setGenericCommand源码

    可以看出调用了genericSetKey函数

    void setGenericCommand(client *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) != C_OK)
                return;
            if (milliseconds <= 0) {
                addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
                return;
            }
            if (unit == UNIT_SECONDS) milliseconds *= 1000;
        }
    
        if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
            (flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
        {
            addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
            return;
        }
        genericSetKey(c,c->db,key,val,flags & OBJ_SET_KEEPTTL,1);
        server.dirty++;
        if (expire) setExpire(c,c->db,key,mstime()+milliseconds);
        notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
        if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC,
            "expire",key,c->db->id);
        addReply(c, ok_reply ? ok_reply : shared.ok);
    }

     genericSetKey源码

    void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
        if (lookupKeyWrite(db,key) == NULL) {
            dbAdd(db,key,val);
        } else {
            dbOverwrite(db,key,val);
        }
        incrRefCount(val);
        if (!keepttl) removeExpire(db,key);
        if (signal) signalModifiedKey(c,db,key);
    }

     dbOverwrite源码

    可以看出最后存入dict中

    void dbOverwrite(redisDb *db, robj *key, robj *val) {
        dictEntry *de = dictFind(db->dict,key->ptr);
    
        serverAssertWithInfo(NULL,key,de != NULL);
        dictEntry auxentry = *de;
        robj *old = dictGetVal(de);
        if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
            val->lru = old->lru;
        }
        dictSetVal(db->dict, de, val);
    
        if (server.lazyfree_lazy_server_del) {
            freeObjAsync(old);
            dictSetVal(db->dict, &auxentry, NULL);
        }
    
        dictFreeVal(db->dict, &auxentry);
    }

    incr/incrby/decr/decrby命令

    incr/decr 自增或者自减1

    incrby/decrby:自增或者自减指定数

    127.0.0.1:6379> INCR userId
    (integer) 2
    127.0.0.1:6379> INCR userId
    (integer) 3
    127.0.0.1:6379> get userId
    "3"
    127.0.0.1:6379> INCRBY userId 10
    (integer) 13
    127.0.0.1:6379> DECR userId
    (integer) 12
    127.0.0.1:6379> DECRBY userId 10
    (integer) 2

    append

    如果 key 已经存在,并且值为字符串,那么这个命令会把 value 追加到原来值(value)的结尾。 如果 key 不存在,那么它将首先创建一个空字符串的key,再执行追加操作,这种情况 APPEND 将类似于 SET 操作。

    127.0.0.1:6379> APPEND myKey "!"
    (integer) 6
    127.0.0.1:6379> get myKey
    "World!"
    127.0.0.1:6379>

    setex/psetex

    设置过期时间,

    setex表示设置多少秒过期

    psetex表示设置多少毫秒过期

    127.0.0.1:6379> psetex mykey 10000 "Hello"
    OK
    127.0.0.1:6379> pttl mykey
    (integer) 3430
    127.0.0.1:6379> ttl mykey
    (integer) -2
    127.0.0.1:6379> pttl mykey
    (integer) -2
    127.0.0.1:6379> setex mykey 10 "Hello"
    OK
    127.0.0.1:6379> ttl mykey
    (integer) 5
    127.0.0.1:6379> ttl mykey
    (integer) -2

    strlen

    返回key的string类型value的长度。如果key对应的非string类型,就返回错误。

    strlen取的是sds中的len属性,所以时间复杂度是O(1)

    时间复杂度:O(1)

    127.0.0.1:6379> set mykey "HelloWorld!"
    OK
    127.0.0.1:6379> strlen mykey
    (integer) 11
    127.0.0.1:6379>

    genrange/setrange

    genrange: 返回key对应的字符串value的子串,这个子串是由start和end位移决定的(两者都在string内)。 相当于C#中的substr

    setrange:这个命令的作用是覆盖key对应的string的一部分,从指定的offset处开始,覆盖value的长度。如果offset比当前key对应string还要长,那这个string后面就补0以达到offset。不存在的keys被认为是空字符串,所以这个命令可以确保key有一个足够大的字符串,能在offset处设置value。 相当于C#中的replace

    127.0.0.1:6379> getrange mykey 0 4
    "Hello"
    127.0.0.1:6379> setrange mykey 5 redis
    (integer) 11
    127.0.0.1:6379> get mykey
    "Helloredis!"
    127.0.0.1:6379>

    源码解析

    size_t stringObjectLen(robj *o) {
        serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);
        if (sdsEncodedObject(o)) {
            return sdslen(o->ptr);
        } else {
            return sdigits10((long)o->ptr);
        }
    }
    static inline size_t sdslen(const sds s) {
        unsigned char flags = s[-1];
        switch(flags&SDS_TYPE_MASK) {
            case SDS_TYPE_5:d
                return SDS_TYPE_5_LEN(flags);
            case SDS_TYPE_8:
                return SDS_HDR(8,s)->len;
            case SDS_TYPE_16:
                return SDS_HDR(16,s)->len;
            case SDS_TYPE_32:
                return SDS_HDR(32,s)->len;
            case SDS_TYPE_64:
                return SDS_HDR(64,s)->len;
        }
        return 0;
    }

    setbit/getbit/bitop

    setbit:设置或者清空key的value(字符串)在offset处的bit值。

    gitbit:返回key对应的string在offset处的bit值 当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。到内存分配。

    对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。

    BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数:

    BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。

    BITOP OR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。

    BITOP XOR destkey srckey1 srckey2 srckey3 ... srckeyN,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。

    BITOP NOT destkey srckey,对给定 key 求逻辑非,并将结果保存到 destkey 。

    127.0.0.1:6379> setbit mykey 7 1
    (integer) 0
    127.0.0.1:6379> getbit mykey 7
    (integer) 1
    127.0.0.1:6379> getbit mykey 0
    (integer) 0
    127.0.0.1:6379> setbit mykey 7 1
    (integer) 1
    127.0.0.1:6379> setbit num1 7 1
    (integer) 0
    127.0.0.1:6379> setbit num2 6 1
    (integer) 0
    127.0.0.1:6379> bitop and nums num1 num2
    (integer) 1
    127.0.0.1:6379> get nums
    "x00"
    127.0.0.1:6379>
  • 相关阅读:
    System Verilog 片断
    如何避免covergroup中出现错误
    一种FPGA图像处理算法的快速验证方式
    什么才是一个feature to be test?
    我的第一份vPlan衍变路线
    思想误区解答:请专注于DUT的功能(全部为菜鸟个人总结不保证正确)
    谈谈验证中的SystemVerilog和CPP//转载
    ResourceBundleViewResolver
    springmvc json数据返回前台,中文乱码
    将字符串中间的某段长度替换成固定的值
  • 原文地址:https://www.cnblogs.com/vic-tory/p/13153375.html
Copyright © 2011-2022 走看看