zoukankan      html  css  js  c++  java
  • 15天玩转redis —— 第五篇 集合对象类型

      这篇我们来看看Redis五大类型中的第四大类型:“集合类型”,集合类型还是蛮有意思的,第一个是因为它算是只使用key的Dictionary简易版,

    这样说来的话,它就比Dictionary节省很多内存消耗,第二个是因为它和C#中的HashSet是一个等同类型,废话不多说,先看redis手册,如下:

    上面就是redis中的set类型使用到的所有方法,还是老话,常用的方法也就那么四个(CURD)。。。

    一: 常用方法

    1. SAdd

      这个方法毫无疑问,就是向集合里面添加数据,比如下面这样,我往fruits集合里面添加喜爱的水果。

    127.0.0.1:6379> sadd fruits apple
    (integer) 1
    127.0.0.1:6379> sadd fruits banana
    (integer) 1
    127.0.0.1:6379> smembers fruits
    1) "banana"
    2) "apple"
    127.0.0.1:6379> 

    上面这个sadd你也看到了,我往集合里面成功添加了两个元素,现在你可能不满足这么简单的添加,你或许想知道set这个集合在redis底层是使用

    什么来实现的,你可以用object encoding查看一下便知:

    127.0.0.1:6379> object encoding fruits
    "hashtable"
    127.0.0.1:6379> 

    看到了吧,是hashtable这个吊毛,现在闭上眼睛都能想到,肯定就是只用key的dictionary啦,对不对,如果你还有疑问的话,我还可以找到底层

    代码给你看,好不啦???

    有没有看到dictAdd方法,而其中的第三个参数正好是Null。。。对应着*val形参,你看牛叉不牛叉。。。然后我再带你看看dictAdd方法的定义。

    好了,关于hashtable的实现理论,我在上一篇文章中也已经说过了,这里就不再赘叙了。

    2. SPOP,SMEMBERS

        既然元素进来了,总不能不出来吧,这里的第一个SPOP:移除并返回集合中的一个随机元素,有一点奇怪的是,这种奇怪的方法其实在我们

    C#中的HashSet并没有好办法解决,就比如”这个随机“就有点烦人了,下面这是我能想到的方法。

    刚才随便插了一句话,下面我们继续SAdd,再SPop出来。

    127.0.0.1:6379> sadd fruits pear
    (integer) 1
    127.0.0.1:6379> sadd fruits grape
    (integer) 1
    127.0.0.1:6379> sadd fruits chestnut
    (integer) 1
    127.0.0.1:6379> smembers fruits
    1) "grape"
    2) "pear"
    3) "banana"
    4) "apple"
    5) "chestnut"
    127.0.0.1:6379> spop fruits
    "apple"
    127.0.0.1:6379> spop fruits
    "chestnut"
    127.0.0.1:6379> smembers fruits
    1) "grape"
    2) "pear"
    3) "banana"
    127.0.0.1:6379> 

    这个方法确实还是蛮好的,起码它是原子性操作,如果要我自己实现的话,起码还是要10行左右代码的。

    3. SREM

        既然说到了CURD,那怎么能少了D呢,它的功能定义就是:移除集合 key 中的一个或多个 member 元素,不存在的 member 元素会被忽略,

    下面我随便举个例子,删除fruits中的pear。

    127.0.0.1:6379> smembers fruits
    1) "grape"
    2) "pear"
    3) "banana"
    127.0.0.1:6379> srem fruits pear
    (integer) 1
    127.0.0.1:6379> smembers fruits
    1) "grape"
    2) "banana"
    127.0.0.1:6379> 

     

       好了,常用的操作就那么几个,是不是觉得好傻瓜哦。。。傻瓜就对了,方法是简单的,关键你需要了解这个方法底层是如何实现的,这样才能做到

    心里有数,就比如Set函数,它的源代码全部都在 “t.set.c” 中。

      1 /*
      2  * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are met:
      7  *
      8  *   * Redistributions of source code must retain the above copyright notice,
      9  *     this list of conditions and the following disclaimer.
     10  *   * Redistributions in binary form must reproduce the above copyright
     11  *     notice, this list of conditions and the following disclaimer in the
     12  *     documentation and/or other materials provided with the distribution.
     13  *   * Neither the name of Redis nor the names of its contributors may be used
     14  *     to endorse or promote products derived from this software without
     15  *     specific prior written permission.
     16  *
     17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
     18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
     21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     27  * POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #include "redis.h"
     31 
     32 /*-----------------------------------------------------------------------------
     33  * Set Commands
     34  *----------------------------------------------------------------------------*/
     35 
     36 void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op);
     37 
     38 /* Factory method to return a set that *can* hold "value". When the object has
     39  * an integer-encodable value, an intset will be returned. Otherwise a regular
     40  * hash table. */
     41 robj *setTypeCreate(robj *value) {
     42     if (isObjectRepresentableAsLongLong(value,NULL) == REDIS_OK)
     43         return createIntsetObject();
     44     return createSetObject();
     45 }
     46 
     47 int setTypeAdd(robj *subject, robj *value) {
     48     long long llval;
     49     if (subject->encoding == REDIS_ENCODING_HT) {
     50         if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
     51             incrRefCount(value);
     52             return 1;
     53         }
     54     } else if (subject->encoding == REDIS_ENCODING_INTSET) {
     55         if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
     56             uint8_t success = 0;
     57             subject->ptr = intsetAdd(subject->ptr,llval,&success);
     58             if (success) {
     59                 /* Convert to regular set when the intset contains
     60                  * too many entries. */
     61                 if (intsetLen(subject->ptr) > server.set_max_intset_entries)
     62                     setTypeConvert(subject,REDIS_ENCODING_HT);
     63                 return 1;
     64             }
     65         } else {
     66             /* Failed to get integer from object, convert to regular set. */
     67             setTypeConvert(subject,REDIS_ENCODING_HT);
     68 
     69             /* The set *was* an intset and this value is not integer
     70              * encodable, so dictAdd should always work. */
     71             redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
     72             incrRefCount(value);
     73             return 1;
     74         }
     75     } else {
     76         redisPanic("Unknown set encoding");
     77     }
     78     return 0;
     79 }
     80 
     81 int setTypeRemove(robj *setobj, robj *value) {
     82     long long llval;
     83     if (setobj->encoding == REDIS_ENCODING_HT) {
     84         if (dictDelete(setobj->ptr,value) == DICT_OK) {
     85             if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
     86             return 1;
     87         }
     88     } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
     89         if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
     90             int success;
     91             setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
     92             if (success) return 1;
     93         }
     94     } else {
     95         redisPanic("Unknown set encoding");
     96     }
     97     return 0;
     98 }
     99 
    100 int setTypeIsMember(robj *subject, robj *value) {
    101     long long llval;
    102     if (subject->encoding == REDIS_ENCODING_HT) {
    103         return dictFind((dict*)subject->ptr,value) != NULL;
    104     } else if (subject->encoding == REDIS_ENCODING_INTSET) {
    105         if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
    106             return intsetFind((intset*)subject->ptr,llval);
    107         }
    108     } else {
    109         redisPanic("Unknown set encoding");
    110     }
    111     return 0;
    112 }
    113 
    114 setTypeIterator *setTypeInitIterator(robj *subject) {
    115     setTypeIterator *si = zmalloc(sizeof(setTypeIterator));
    116     si->subject = subject;
    117     si->encoding = subject->encoding;
    118     if (si->encoding == REDIS_ENCODING_HT) {
    119         si->di = dictGetIterator(subject->ptr);
    120     } else if (si->encoding == REDIS_ENCODING_INTSET) {
    121         si->ii = 0;
    122     } else {
    123         redisPanic("Unknown set encoding");
    124     }
    125     return si;
    126 }
    127 
    128 void setTypeReleaseIterator(setTypeIterator *si) {
    129     if (si->encoding == REDIS_ENCODING_HT)
    130         dictReleaseIterator(si->di);
    131     zfree(si);
    132 }
    133 
    134 /* Move to the next entry in the set. Returns the object at the current
    135  * position.
    136  *
    137  * Since set elements can be internally be stored as redis objects or
    138  * simple arrays of integers, setTypeNext returns the encoding of the
    139  * set object you are iterating, and will populate the appropriate pointer
    140  * (eobj) or (llobj) accordingly.
    141  *
    142  * When there are no longer elements -1 is returned.
    143  * Returned objects ref count is not incremented, so this function is
    144  * copy on write friendly. */
    145 int setTypeNext(setTypeIterator *si, robj **objele, int64_t *llele) {
    146     if (si->encoding == REDIS_ENCODING_HT) {
    147         dictEntry *de = dictNext(si->di);
    148         if (de == NULL) return -1;
    149         *objele = dictGetKey(de);
    150     } else if (si->encoding == REDIS_ENCODING_INTSET) {
    151         if (!intsetGet(si->subject->ptr,si->ii++,llele))
    152             return -1;
    153     }
    154     return si->encoding;
    155 }
    156 
    157 /* The not copy on write friendly version but easy to use version
    158  * of setTypeNext() is setTypeNextObject(), returning new objects
    159  * or incrementing the ref count of returned objects. So if you don't
    160  * retain a pointer to this object you should call decrRefCount() against it.
    161  *
    162  * This function is the way to go for write operations where COW is not
    163  * an issue as the result will be anyway of incrementing the ref count. */
    164 robj *setTypeNextObject(setTypeIterator *si) {
    165     int64_t intele;
    166     robj *objele;
    167     int encoding;
    168 
    169     encoding = setTypeNext(si,&objele,&intele);
    170     switch(encoding) {
    171         case -1:    return NULL;
    172         case REDIS_ENCODING_INTSET:
    173             return createStringObjectFromLongLong(intele);
    174         case REDIS_ENCODING_HT:
    175             incrRefCount(objele);
    176             return objele;
    177         default:
    178             redisPanic("Unsupported encoding");
    179     }
    180     return NULL; /* just to suppress warnings */
    181 }
    182 
    183 /* Return random element from a non empty set.
    184  * The returned element can be a int64_t value if the set is encoded
    185  * as an "intset" blob of integers, or a redis object if the set
    186  * is a regular set.
    187  *
    188  * The caller provides both pointers to be populated with the right
    189  * object. The return value of the function is the object->encoding
    190  * field of the object and is used by the caller to check if the
    191  * int64_t pointer or the redis object pointer was populated.
    192  *
    193  * When an object is returned (the set was a real set) the ref count
    194  * of the object is not incremented so this function can be considered
    195  * copy on write friendly. */
    196 int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
    197     if (setobj->encoding == REDIS_ENCODING_HT) {
    198         dictEntry *de = dictGetRandomKey(setobj->ptr);
    199         *objele = dictGetKey(de);
    200     } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
    201         *llele = intsetRandom(setobj->ptr);
    202     } else {
    203         redisPanic("Unknown set encoding");
    204     }
    205     return setobj->encoding;
    206 }
    207 
    208 unsigned long setTypeSize(robj *subject) {
    209     if (subject->encoding == REDIS_ENCODING_HT) {
    210         return dictSize((dict*)subject->ptr);
    211     } else if (subject->encoding == REDIS_ENCODING_INTSET) {
    212         return intsetLen((intset*)subject->ptr);
    213     } else {
    214         redisPanic("Unknown set encoding");
    215     }
    216 }
    217 
    218 /* Convert the set to specified encoding. The resulting dict (when converting
    219  * to a hash table) is presized to hold the number of elements in the original
    220  * set. */
    221 void setTypeConvert(robj *setobj, int enc) {
    222     setTypeIterator *si;
    223     redisAssertWithInfo(NULL,setobj,setobj->type == REDIS_SET &&
    224                              setobj->encoding == REDIS_ENCODING_INTSET);
    225 
    226     if (enc == REDIS_ENCODING_HT) {
    227         int64_t intele;
    228         dict *d = dictCreate(&setDictType,NULL);
    229         robj *element;
    230 
    231         /* Presize the dict to avoid rehashing */
    232         dictExpand(d,intsetLen(setobj->ptr));
    233 
    234         /* To add the elements we extract integers and create redis objects */
    235         si = setTypeInitIterator(setobj);
    236         while (setTypeNext(si,NULL,&intele) != -1) {
    237             element = createStringObjectFromLongLong(intele);
    238             redisAssertWithInfo(NULL,element,dictAdd(d,element,NULL) == DICT_OK);
    239         }
    240         setTypeReleaseIterator(si);
    241 
    242         setobj->encoding = REDIS_ENCODING_HT;
    243         zfree(setobj->ptr);
    244         setobj->ptr = d;
    245     } else {
    246         redisPanic("Unsupported set conversion");
    247     }
    248 }
    249 
    250 void saddCommand(redisClient *c) {
    251     robj *set;
    252     int j, added = 0;
    253 
    254     set = lookupKeyWrite(c->db,c->argv[1]);
    255     if (set == NULL) {
    256         set = setTypeCreate(c->argv[2]);
    257         dbAdd(c->db,c->argv[1],set);
    258     } else {
    259         if (set->type != REDIS_SET) {
    260             addReply(c,shared.wrongtypeerr);
    261             return;
    262         }
    263     }
    264 
    265     for (j = 2; j < c->argc; j++) {
    266         c->argv[j] = tryObjectEncoding(c->argv[j]);
    267         if (setTypeAdd(set,c->argv[j])) added++;
    268     }
    269     if (added) {
    270         signalModifiedKey(c->db,c->argv[1]);
    271         notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
    272     }
    273     server.dirty += added;
    274     addReplyLongLong(c,added);
    275 }
    276 
    277 void sremCommand(redisClient *c) {
    278     robj *set;
    279     int j, deleted = 0, keyremoved = 0;
    280 
    281     if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
    282         checkType(c,set,REDIS_SET)) return;
    283 
    284     for (j = 2; j < c->argc; j++) {
    285         if (setTypeRemove(set,c->argv[j])) {
    286             deleted++;
    287             if (setTypeSize(set) == 0) {
    288                 dbDelete(c->db,c->argv[1]);
    289                 keyremoved = 1;
    290                 break;
    291             }
    292         }
    293     }
    294     if (deleted) {
    295         signalModifiedKey(c->db,c->argv[1]);
    296         notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
    297         if (keyremoved)
    298             notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
    299                                 c->db->id);
    300         server.dirty += deleted;
    301     }
    302     addReplyLongLong(c,deleted);
    303 }
    304 
    305 void smoveCommand(redisClient *c) {
    306     robj *srcset, *dstset, *ele;
    307     srcset = lookupKeyWrite(c->db,c->argv[1]);
    308     dstset = lookupKeyWrite(c->db,c->argv[2]);
    309     ele = c->argv[3] = tryObjectEncoding(c->argv[3]);
    310 
    311     /* If the source key does not exist return 0 */
    312     if (srcset == NULL) {
    313         addReply(c,shared.czero);
    314         return;
    315     }
    316 
    317     /* If the source key has the wrong type, or the destination key
    318      * is set and has the wrong type, return with an error. */
    319     if (checkType(c,srcset,REDIS_SET) ||
    320         (dstset && checkType(c,dstset,REDIS_SET))) return;
    321 
    322     /* If srcset and dstset are equal, SMOVE is a no-op */
    323     if (srcset == dstset) {
    324         addReply(c,setTypeIsMember(srcset,ele) ? shared.cone : shared.czero);
    325         return;
    326     }
    327 
    328     /* If the element cannot be removed from the src set, return 0. */
    329     if (!setTypeRemove(srcset,ele)) {
    330         addReply(c,shared.czero);
    331         return;
    332     }
    333     notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
    334 
    335     /* Remove the src set from the database when empty */
    336     if (setTypeSize(srcset) == 0) {
    337         dbDelete(c->db,c->argv[1]);
    338         notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
    339     }
    340     signalModifiedKey(c->db,c->argv[1]);
    341     signalModifiedKey(c->db,c->argv[2]);
    342     server.dirty++;
    343 
    344     /* Create the destination set when it doesn't exist */
    345     if (!dstset) {
    346         dstset = setTypeCreate(ele);
    347         dbAdd(c->db,c->argv[2],dstset);
    348     }
    349 
    350     /* An extra key has changed when ele was successfully added to dstset */
    351     if (setTypeAdd(dstset,ele)) {
    352         server.dirty++;
    353         notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
    354     }
    355     addReply(c,shared.cone);
    356 }
    357 
    358 void sismemberCommand(redisClient *c) {
    359     robj *set;
    360 
    361     if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
    362         checkType(c,set,REDIS_SET)) return;
    363 
    364     c->argv[2] = tryObjectEncoding(c->argv[2]);
    365     if (setTypeIsMember(set,c->argv[2]))
    366         addReply(c,shared.cone);
    367     else
    368         addReply(c,shared.czero);
    369 }
    370 
    371 void scardCommand(redisClient *c) {
    372     robj *o;
    373 
    374     if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
    375         checkType(c,o,REDIS_SET)) return;
    376 
    377     addReplyLongLong(c,setTypeSize(o));
    378 }
    379 
    380 void spopCommand(redisClient *c) {
    381     robj *set, *ele, *aux;
    382     int64_t llele;
    383     int encoding;
    384 
    385     if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
    386         checkType(c,set,REDIS_SET)) return;
    387 
    388     encoding = setTypeRandomElement(set,&ele,&llele);
    389     if (encoding == REDIS_ENCODING_INTSET) {
    390         ele = createStringObjectFromLongLong(llele);
    391         set->ptr = intsetRemove(set->ptr,llele,NULL);
    392     } else {
    393         incrRefCount(ele);
    394         setTypeRemove(set,ele);
    395     }
    396     notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
    397 
    398     /* Replicate/AOF this command as an SREM operation */
    399     aux = createStringObject("SREM",4);
    400     rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
    401     decrRefCount(ele);
    402     decrRefCount(aux);
    403 
    404     addReplyBulk(c,ele);
    405     if (setTypeSize(set) == 0) {
    406         dbDelete(c->db,c->argv[1]);
    407         notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
    408     }
    409     signalModifiedKey(c->db,c->argv[1]);
    410     server.dirty++;
    411 }
    412 
    413 /* handle the "SRANDMEMBER key <count>" variant. The normal version of the
    414  * command is handled by the srandmemberCommand() function itself. */
    415 
    416 /* How many times bigger should be the set compared to the requested size
    417  * for us to don't use the "remove elements" strategy? Read later in the
    418  * implementation for more info. */
    419 #define SRANDMEMBER_SUB_STRATEGY_MUL 3
    420 
    421 void srandmemberWithCountCommand(redisClient *c) {
    422     long l;
    423     unsigned long count, size;
    424     int uniq = 1;
    425     robj *set, *ele;
    426     int64_t llele;
    427     int encoding;
    428 
    429     dict *d;
    430 
    431     if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != REDIS_OK) return;
    432     if (l >= 0) {
    433         count = (unsigned) l;
    434     } else {
    435         /* A negative count means: return the same elements multiple times
    436          * (i.e. don't remove the extracted element after every extraction). */
    437         count = -l;
    438         uniq = 0;
    439     }
    440 
    441     if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
    442         == NULL || checkType(c,set,REDIS_SET)) return;
    443     size = setTypeSize(set);
    444 
    445     /* If count is zero, serve it ASAP to avoid special cases later. */
    446     if (count == 0) {
    447         addReply(c,shared.emptymultibulk);
    448         return;
    449     }
    450 
    451     /* CASE 1: The count was negative, so the extraction method is just:
    452      * "return N random elements" sampling the whole set every time.
    453      * This case is trivial and can be served without auxiliary data
    454      * structures. */
    455     if (!uniq) {
    456         addReplyMultiBulkLen(c,count);
    457         while(count--) {
    458             encoding = setTypeRandomElement(set,&ele,&llele);
    459             if (encoding == REDIS_ENCODING_INTSET) {
    460                 addReplyBulkLongLong(c,llele);
    461             } else {
    462                 addReplyBulk(c,ele);
    463             }
    464         }
    465         return;
    466     }
    467 
    468     /* CASE 2:
    469      * The number of requested elements is greater than the number of
    470      * elements inside the set: simply return the whole set. */
    471     if (count >= size) {
    472         sunionDiffGenericCommand(c,c->argv+1,1,NULL,REDIS_OP_UNION);
    473         return;
    474     }
    475 
    476     /* For CASE 3 and CASE 4 we need an auxiliary dictionary. */
    477     d = dictCreate(&setDictType,NULL);
    478 
    479     /* CASE 3:
    480      * The number of elements inside the set is not greater than
    481      * SRANDMEMBER_SUB_STRATEGY_MUL times the number of requested elements.
    482      * In this case we create a set from scratch with all the elements, and
    483      * subtract random elements to reach the requested number of elements.
    484      *
    485      * This is done because if the number of requsted elements is just
    486      * a bit less than the number of elements in the set, the natural approach
    487      * used into CASE 3 is highly inefficient. */
    488     if (count*SRANDMEMBER_SUB_STRATEGY_MUL > size) {
    489         setTypeIterator *si;
    490 
    491         /* Add all the elements into the temporary dictionary. */
    492         si = setTypeInitIterator(set);
    493         while((encoding = setTypeNext(si,&ele,&llele)) != -1) {
    494             int retval = DICT_ERR;
    495 
    496             if (encoding == REDIS_ENCODING_INTSET) {
    497                 retval = dictAdd(d,createStringObjectFromLongLong(llele),NULL);
    498             } else {
    499                 retval = dictAdd(d,dupStringObject(ele),NULL);
    500             }
    501             redisAssert(retval == DICT_OK);
    502         }
    503         setTypeReleaseIterator(si);
    504         redisAssert(dictSize(d) == size);
    505 
    506         /* Remove random elements to reach the right count. */
    507         while(size > count) {
    508             dictEntry *de;
    509 
    510             de = dictGetRandomKey(d);
    511             dictDelete(d,dictGetKey(de));
    512             size--;
    513         }
    514     }
    515 
    516     /* CASE 4: We have a big set compared to the requested number of elements.
    517      * In this case we can simply get random elements from the set and add
    518      * to the temporary set, trying to eventually get enough unique elements
    519      * to reach the specified count. */
    520     else {
    521         unsigned long added = 0;
    522 
    523         while(added < count) {
    524             encoding = setTypeRandomElement(set,&ele,&llele);
    525             if (encoding == REDIS_ENCODING_INTSET) {
    526                 ele = createStringObjectFromLongLong(llele);
    527             } else {
    528                 ele = dupStringObject(ele);
    529             }
    530             /* Try to add the object to the dictionary. If it already exists
    531              * free it, otherwise increment the number of objects we have
    532              * in the result dictionary. */
    533             if (dictAdd(d,ele,NULL) == DICT_OK)
    534                 added++;
    535             else
    536                 decrRefCount(ele);
    537         }
    538     }
    539 
    540     /* CASE 3 & 4: send the result to the user. */
    541     {
    542         dictIterator *di;
    543         dictEntry *de;
    544 
    545         addReplyMultiBulkLen(c,count);
    546         di = dictGetIterator(d);
    547         while((de = dictNext(di)) != NULL)
    548             addReplyBulk(c,dictGetKey(de));
    549         dictReleaseIterator(di);
    550         dictRelease(d);
    551     }
    552 }
    553 
    554 void srandmemberCommand(redisClient *c) {
    555     robj *set, *ele;
    556     int64_t llele;
    557     int encoding;
    558 
    559     if (c->argc == 3) {
    560         srandmemberWithCountCommand(c);
    561         return;
    562     } else if (c->argc > 3) {
    563         addReply(c,shared.syntaxerr);
    564         return;
    565     }
    566 
    567     if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
    568         checkType(c,set,REDIS_SET)) return;
    569 
    570     encoding = setTypeRandomElement(set,&ele,&llele);
    571     if (encoding == REDIS_ENCODING_INTSET) {
    572         addReplyBulkLongLong(c,llele);
    573     } else {
    574         addReplyBulk(c,ele);
    575     }
    576 }
    577 
    578 int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
    579     return setTypeSize(*(robj**)s1)-setTypeSize(*(robj**)s2);
    580 }
    581 
    582 /* This is used by SDIFF and in this case we can receive NULL that should
    583  * be handled as empty sets. */
    584 int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) {
    585     robj *o1 = *(robj**)s1, *o2 = *(robj**)s2;
    586 
    587     return  (o2 ? setTypeSize(o2) : 0) - (o1 ? setTypeSize(o1) : 0);
    588 }
    589 
    590 void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
    591     robj **sets = zmalloc(sizeof(robj*)*setnum);
    592     setTypeIterator *si;
    593     robj *eleobj, *dstset = NULL;
    594     int64_t intobj;
    595     void *replylen = NULL;
    596     unsigned long j, cardinality = 0;
    597     int encoding;
    598 
    599     for (j = 0; j < setnum; j++) {
    600         robj *setobj = dstkey ?
    601             lookupKeyWrite(c->db,setkeys[j]) :
    602             lookupKeyRead(c->db,setkeys[j]);
    603         if (!setobj) {
    604             zfree(sets);
    605             if (dstkey) {
    606                 if (dbDelete(c->db,dstkey)) {
    607                     signalModifiedKey(c->db,dstkey);
    608                     server.dirty++;
    609                 }
    610                 addReply(c,shared.czero);
    611             } else {
    612                 addReply(c,shared.emptymultibulk);
    613             }
    614             return;
    615         }
    616         if (checkType(c,setobj,REDIS_SET)) {
    617             zfree(sets);
    618             return;
    619         }
    620         sets[j] = setobj;
    621     }
    622     /* Sort sets from the smallest to largest, this will improve our
    623      * algorithm's performance */
    624     qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
    625 
    626     /* The first thing we should output is the total number of elements...
    627      * since this is a multi-bulk write, but at this stage we don't know
    628      * the intersection set size, so we use a trick, append an empty object
    629      * to the output list and save the pointer to later modify it with the
    630      * right length */
    631     if (!dstkey) {
    632         replylen = addDeferredMultiBulkLength(c);
    633     } else {
    634         /* If we have a target key where to store the resulting set
    635          * create this key with an empty set inside */
    636         dstset = createIntsetObject();
    637     }
    638 
    639     /* Iterate all the elements of the first (smallest) set, and test
    640      * the element against all the other sets, if at least one set does
    641      * not include the element it is discarded */
    642     si = setTypeInitIterator(sets[0]);
    643     while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
    644         for (j = 1; j < setnum; j++) {
    645             if (sets[j] == sets[0]) continue;
    646             if (encoding == REDIS_ENCODING_INTSET) {
    647                 /* intset with intset is simple... and fast */
    648                 if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
    649                     !intsetFind((intset*)sets[j]->ptr,intobj))
    650                 {
    651                     break;
    652                 /* in order to compare an integer with an object we
    653                  * have to use the generic function, creating an object
    654                  * for this */
    655                 } else if (sets[j]->encoding == REDIS_ENCODING_HT) {
    656                     eleobj = createStringObjectFromLongLong(intobj);
    657                     if (!setTypeIsMember(sets[j],eleobj)) {
    658                         decrRefCount(eleobj);
    659                         break;
    660                     }
    661                     decrRefCount(eleobj);
    662                 }
    663             } else if (encoding == REDIS_ENCODING_HT) {
    664                 /* Optimization... if the source object is integer
    665                  * encoded AND the target set is an intset, we can get
    666                  * a much faster path. */
    667                 if (eleobj->encoding == REDIS_ENCODING_INT &&
    668                     sets[j]->encoding == REDIS_ENCODING_INTSET &&
    669                     !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
    670                 {
    671                     break;
    672                 /* else... object to object check is easy as we use the
    673                  * type agnostic API here. */
    674                 } else if (!setTypeIsMember(sets[j],eleobj)) {
    675                     break;
    676                 }
    677             }
    678         }
    679 
    680         /* Only take action when all sets contain the member */
    681         if (j == setnum) {
    682             if (!dstkey) {
    683                 if (encoding == REDIS_ENCODING_HT)
    684                     addReplyBulk(c,eleobj);
    685                 else
    686                     addReplyBulkLongLong(c,intobj);
    687                 cardinality++;
    688             } else {
    689                 if (encoding == REDIS_ENCODING_INTSET) {
    690                     eleobj = createStringObjectFromLongLong(intobj);
    691                     setTypeAdd(dstset,eleobj);
    692                     decrRefCount(eleobj);
    693                 } else {
    694                     setTypeAdd(dstset,eleobj);
    695                 }
    696             }
    697         }
    698     }
    699     setTypeReleaseIterator(si);
    700 
    701     if (dstkey) {
    702         /* Store the resulting set into the target, if the intersection
    703          * is not an empty set. */
    704         int deleted = dbDelete(c->db,dstkey);
    705         if (setTypeSize(dstset) > 0) {
    706             dbAdd(c->db,dstkey,dstset);
    707             addReplyLongLong(c,setTypeSize(dstset));
    708             notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore",
    709                 dstkey,c->db->id);
    710         } else {
    711             decrRefCount(dstset);
    712             addReply(c,shared.czero);
    713             if (deleted)
    714                 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
    715                     dstkey,c->db->id);
    716         }
    717         signalModifiedKey(c->db,dstkey);
    718         server.dirty++;
    719     } else {
    720         setDeferredMultiBulkLength(c,replylen,cardinality);
    721     }
    722     zfree(sets);
    723 }
    724 
    725 void sinterCommand(redisClient *c) {
    726     sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
    727 }
    728 
    729 void sinterstoreCommand(redisClient *c) {
    730     sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
    731 }
    732 
    733 #define REDIS_OP_UNION 0
    734 #define REDIS_OP_DIFF 1
    735 #define REDIS_OP_INTER 2
    736 
    737 void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
    738     robj **sets = zmalloc(sizeof(robj*)*setnum);
    739     setTypeIterator *si;
    740     robj *ele, *dstset = NULL;
    741     int j, cardinality = 0;
    742     int diff_algo = 1;
    743 
    744     for (j = 0; j < setnum; j++) {
    745         robj *setobj = dstkey ?
    746             lookupKeyWrite(c->db,setkeys[j]) :
    747             lookupKeyRead(c->db,setkeys[j]);
    748         if (!setobj) {
    749             sets[j] = NULL;
    750             continue;
    751         }
    752         if (checkType(c,setobj,REDIS_SET)) {
    753             zfree(sets);
    754             return;
    755         }
    756         sets[j] = setobj;
    757     }
    758 
    759     /* Select what DIFF algorithm to use.
    760      *
    761      * Algorithm 1 is O(N*M) where N is the size of the element first set
    762      * and M the total number of sets.
    763      *
    764      * Algorithm 2 is O(N) where N is the total number of elements in all
    765      * the sets.
    766      *
    767      * We compute what is the best bet with the current input here. */
    768     if (op == REDIS_OP_DIFF && sets[0]) {
    769         long long algo_one_work = 0, algo_two_work = 0;
    770 
    771         for (j = 0; j < setnum; j++) {
    772             if (sets[j] == NULL) continue;
    773 
    774             algo_one_work += setTypeSize(sets[0]);
    775             algo_two_work += setTypeSize(sets[j]);
    776         }
    777 
    778         /* Algorithm 1 has better constant times and performs less operations
    779          * if there are elements in common. Give it some advantage. */
    780         algo_one_work /= 2;
    781         diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;
    782 
    783         if (diff_algo == 1 && setnum > 1) {
    784             /* With algorithm 1 it is better to order the sets to subtract
    785              * by decreasing size, so that we are more likely to find
    786              * duplicated elements ASAP. */
    787             qsort(sets+1,setnum-1,sizeof(robj*),
    788                 qsortCompareSetsByRevCardinality);
    789         }
    790     }
    791 
    792     /* We need a temp set object to store our union. If the dstkey
    793      * is not NULL (that is, we are inside an SUNIONSTORE operation) then
    794      * this set object will be the resulting object to set into the target key*/
    795     dstset = createIntsetObject();
    796 
    797     if (op == REDIS_OP_UNION) {
    798         /* Union is trivial, just add every element of every set to the
    799          * temporary set. */
    800         for (j = 0; j < setnum; j++) {
    801             if (!sets[j]) continue; /* non existing keys are like empty sets */
    802 
    803             si = setTypeInitIterator(sets[j]);
    804             while((ele = setTypeNextObject(si)) != NULL) {
    805                 if (setTypeAdd(dstset,ele)) cardinality++;
    806                 decrRefCount(ele);
    807             }
    808             setTypeReleaseIterator(si);
    809         }
    810     } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) {
    811         /* DIFF Algorithm 1:
    812          *
    813          * We perform the diff by iterating all the elements of the first set,
    814          * and only adding it to the target set if the element does not exist
    815          * into all the other sets.
    816          *
    817          * This way we perform at max N*M operations, where N is the size of
    818          * the first set, and M the number of sets. */
    819         si = setTypeInitIterator(sets[0]);
    820         while((ele = setTypeNextObject(si)) != NULL) {
    821             for (j = 1; j < setnum; j++) {
    822                 if (!sets[j]) continue; /* no key is an empty set. */
    823                 if (sets[j] == sets[0]) break; /* same set! */
    824                 if (setTypeIsMember(sets[j],ele)) break;
    825             }
    826             if (j == setnum) {
    827                 /* There is no other set with this element. Add it. */
    828                 setTypeAdd(dstset,ele);
    829                 cardinality++;
    830             }
    831             decrRefCount(ele);
    832         }
    833         setTypeReleaseIterator(si);
    834     } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) {
    835         /* DIFF Algorithm 2:
    836          *
    837          * Add all the elements of the first set to the auxiliary set.
    838          * Then remove all the elements of all the next sets from it.
    839          *
    840          * This is O(N) where N is the sum of all the elements in every
    841          * set. */
    842         for (j = 0; j < setnum; j++) {
    843             if (!sets[j]) continue; /* non existing keys are like empty sets */
    844 
    845             si = setTypeInitIterator(sets[j]);
    846             while((ele = setTypeNextObject(si)) != NULL) {
    847                 if (j == 0) {
    848                     if (setTypeAdd(dstset,ele)) cardinality++;
    849                 } else {
    850                     if (setTypeRemove(dstset,ele)) cardinality--;
    851                 }
    852                 decrRefCount(ele);
    853             }
    854             setTypeReleaseIterator(si);
    855 
    856             /* Exit if result set is empty as any additional removal
    857              * of elements will have no effect. */
    858             if (cardinality == 0) break;
    859         }
    860     }
    861 
    862     /* Output the content of the resulting set, if not in STORE mode */
    863     if (!dstkey) {
    864         addReplyMultiBulkLen(c,cardinality);
    865         si = setTypeInitIterator(dstset);
    866         while((ele = setTypeNextObject(si)) != NULL) {
    867             addReplyBulk(c,ele);
    868             decrRefCount(ele);
    869         }
    870         setTypeReleaseIterator(si);
    871         decrRefCount(dstset);
    872     } else {
    873         /* If we have a target key where to store the resulting set
    874          * create this key with the result set inside */
    875         int deleted = dbDelete(c->db,dstkey);
    876         if (setTypeSize(dstset) > 0) {
    877             dbAdd(c->db,dstkey,dstset);
    878             addReplyLongLong(c,setTypeSize(dstset));
    879             notifyKeyspaceEvent(REDIS_NOTIFY_SET,
    880                 op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore",
    881                 dstkey,c->db->id);
    882         } else {
    883             decrRefCount(dstset);
    884             addReply(c,shared.czero);
    885             if (deleted)
    886                 notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
    887                     dstkey,c->db->id);
    888         }
    889         signalModifiedKey(c->db,dstkey);
    890         server.dirty++;
    891     }
    892     zfree(sets);
    893 }
    894 
    895 void sunionCommand(redisClient *c) {
    896     sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_UNION);
    897 }
    898 
    899 void sunionstoreCommand(redisClient *c) {
    900     sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_UNION);
    901 }
    902 
    903 void sdiffCommand(redisClient *c) {
    904     sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_DIFF);
    905 }
    906 
    907 void sdiffstoreCommand(redisClient *c) {
    908     sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF);
    909 }
    910 
    911 void sscanCommand(redisClient *c) {
    912     robj *set;
    913     unsigned long cursor;
    914 
    915     if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
    916     if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
    917         checkType(c,set,REDIS_SET)) return;
    918     scanGenericCommand(c,set,cursor);
    919 }
    View Code
  • 相关阅读:
    systemctl命令
    linux下常用命令查看端口占用
    【PostgreSQL】存取jsonb
    tomcat内存溢出之PermGen space
    Spring事务传播机制
    java框架篇---spring aop两种配置方式
    Hibernate一对多实例
    Github 的系统内部都在用什么开源软件?
    这是一个关于软件开发的博客。
    JavaScript中数组的集合和映射
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/4978156.html
Copyright © 2011-2022 走看看