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>
原文地址:https://www.cnblogs.com/vic-tory/p/13153375.html