加载中...

第五篇 集合对象类型


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

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

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

 

一: 常用方法

1. SAdd

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

  1. 127.0.0.1:6379> sadd fruits apple
  2. (integer) 1
  3. 127.0.0.1:6379> sadd fruits banana
  4. (integer) 1
  5. 127.0.0.1:6379> smembers fruits
  6. 1) "banana"
  7. 2) "apple"
  8. 127.0.0.1:6379>

 

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

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

  1. 127.0.0.1:6379> object encoding fruits
  2. "hashtable"
  3. 127.0.0.1:6379> 

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

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

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

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

2. SPOP,SMEMBERS

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

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

 

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

  1. 127.0.0.1:6379> sadd fruits pear
  2. (integer) 1
  3. 127.0.0.1:6379> sadd fruits grape
  4. (integer) 1
  5. 127.0.0.1:6379> sadd fruits chestnut
  6. (integer) 1
  7. 127.0.0.1:6379> smembers fruits
  8. 1) "grape"
  9. 2) "pear"
  10. 3) "banana"
  11. 4) "apple"
  12. 5) "chestnut"
  13. 127.0.0.1:6379> spop fruits
  14. "apple"
  15. 127.0.0.1:6379> spop fruits
  16. "chestnut"
  17. 127.0.0.1:6379> smembers fruits
  18. 1) "grape"
  19. 2) "pear"
  20. 3) "banana"
  21. 127.0.0.1:6379>

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

3. SREM

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

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

  1. 127.0.0.1:6379> smembers fruits
  2. 1) "grape"
  3. 2) "pear"
  4. 3) "banana"
  5. 127.0.0.1:6379> srem fruits pear
  6. (integer) 1
  7. 127.0.0.1:6379> smembers fruits
  8. 1) "grape"
  9. 2) "banana"
  10. 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. }

 


还没有评论.