本文及后续文章,Redis版本均是v3.2.8

上文我们说到intset整型集合的数据结构定义即元素的添加和查询操作,本文我们来看下Redis暴露给外面使用的Set集合,先通过一些基本的命令回顾下set

一、set底层数据结构

我们查阅Redis Set命令文档知道:

  • sadd用于分别向集合 myset和myset2中添加元素。添加的元素既有数字,也有非数字(”a”和”b”)。

  • sismember用于判断指定的元素是否在集合内存在。

  • sinter, sunion和sdiff分别用于计算集合的交集、并集和差集。

我们上文提到,set的底层实现,随着元素类型是否是整型以及添加的元素的数目多少,而有所变化。如,上述命令的执行过程中,集合myset的底层数据结构会发生如下变化:

  • 在开始执行完sadd myset 1 2之后,由于添加的都是比较小的整数,所以myset底层是一个intset,其数据编码encoding = 2。

  • 在执行完sadd myset 10000之后,myset底层仍然是一个intset,但其数据编码encoding从2升级到了4。

  • 在执行完sadd myset a b之后,由于添加的元素不再是数字,myset底层的实现会转成一个dict。

我们知道,dict是一个用于维护key和value映射关系的数据结构,那么当set底层用dict表示的时候,它的key和value分别是什么呢?实际上,key就是要添加的集合元素,而value是NULL。

除了前面提到的由于添加非数字元素造成集合底层由intset转成dict之外,还有两种情况可能造成这种转换:

  • 添加了一个数字,但它无法用64bit的有符号数来表达。intset能够表达的最大的整数范围为-264~264-1,因此,如果添加的数字超出了这个范围,这也会导致intset转成dict。

  • 添加的集合元素个数超过了set-max-intset-entries配置的值的时候,也会导致intset转成dict(具体的触发条件参见t_set.c中的setTypeAdd相关代码)。

对于小集合使用intset来存储,主要的原因是节省内存。特别是当存储的元素个数较少的时候,dict所带来的内存开销要大得多(包含两个哈希表、链表指针以及大量的其它元数据)。所以,当存储大量的小集合而且集合元素都是数字的时候,用intset能节省内存空间。

实际上,从时间复杂度上比较,intset的平均情况是没有dict性能高的。以查找为例,intset是O(log n)的,而dict可以认为是O(1)的。但是,由于使用intset的时候集合元素个数比较少,所以这个影响不大。

二、Redis set的并、交、差算法

Redis set的并、交、差算法的实现代码,在t_set.c中。其中计算交集调用的是sinterGenericCommand,计算并集和差集调用的是sunionDiffGenericCommand。它们都能同时对多个(可以多于2个)集合进行运算。当对多个集合进行差集运算时,它表达的含义是:用第一个集合与第二个集合做差集,所得结果再与第三个集合做差集,依次向后类推。

我们在这里简要介绍一下三个算法的实现思路。

交集

计算交集的过程大概可以分为三部分:

  1. 检查各个集合,对于不存在的集合当做空集来处理。一旦出现空集,则不用继续计算了,最终的交集就是空集。

  2. 对各个集合按照元素个数由少到多进行排序。这个排序有利于后面计算的时候从最小的集合开始,需要处理的元素个数较少。

  3. 对排序后第一个集合(也就是最小集合)进行遍历,对于它的每一个元素,依次在后面的所有集合中进行查找。只有在所有集合中都能找到的元素,才加入到最后的结果集合中。

需要注意的是,上述第3步在集合中进行查找,对于intset和dict的存储来说时间复杂度分别是O(log n)和O(1)。但由于只有小集合才使用intset,所以可以粗略地认为intset的查找也是常数时间复杂度的。因此,如Redis官方文档上所说(http://redis.io/commands/sinter),sinter命令的时间复杂度为:

O(N*M) worst case where N is the cardinality of the smallest set and M is the number of sets.

并集

计算并集最简单,只需要遍历所有集合,将每一个元素都添加到最后的结果集合中。向集合中添加元素会自动去重。

由于要遍历所有集合的每个元素,所以Redis官方文档给出的sunion命令的时间复杂度为(http://redis.io/commands/sunion):

O(N) where N is the total number of elements in all given sets.

注意,这里同前面讨论交集计算一样,将元素插入到结果集合的过程,忽略intset的情况,认为时间复杂度为O(1)。

差集

计算差集有两种可能的算法,它们的时间复杂度有所区别。

第一种算法:

  • 对第一个集合进行遍历,对于它的每一个元素,依次在后面的所有集合中进行查找。只有在所有集合中都找不到的元素,才加入到最后的结果集合中。

这种算法的时间复杂度为O(N*M),其中N是第一个集合的元素个数,M是集合数目。

第二种算法:

  • 将第一个集合的所有元素都加入到一个中间集合中。

  • 遍历后面所有的集合,对于碰到的每一个元素,从中间集合中删掉它。

  • 最后中间集合剩下的元素就构成了差集。

这种算法的时间复杂度为O(N),其中N是所有集合的元素个数总和。

在计算差集的开始部分,会先分别估算一下两种算法预期的时间复杂度,然后选择复杂度低的算法来进行运算。还有两点需要注意:

  • 在一定程度上优先选择第一种算法,因为它涉及到的操作比较少,只用添加,而第二种算法要先添加再删除。

  • 如果选择了第一种算法,那么在执行该算法之前,Redis的实现中对于第二个集合之后的所有集合,按照元素个数由多到少进行了排序。这个排序有利于以更大的概率查找到元素,从而更快地结束查找。

对于sdiff的时间复杂度,Redis官方文档(http://redis.io/commands/sdiff)只给出了第二种算法的结果,是不准确的。

三、基本命令

下表列出了与集合相关的一些基本命令。

序号 命令 说明
1 SADD key member1 [member2] 将一个或多个成员添加到集合
2 SCARD key 获取集合中的成员数
3 SDIFF key1 [key2] 减去多个集合
4 SDIFFSTORE destination key1 [key2] 减去多个集并将结果集存储在键中
5 SINTER key1 [key2] 相交多个集合
6 SINTERSTORE destination key1 [key2] 交叉多个集合并将结果集存储在键中
7 SISMEMBER key member 判断确定给定值是否是集合的成员
8 SMOVE source destination member 将成员从一个集合移动到另一个集合
9 SPOP key 从集合中删除并返回随机成员
10 SRANDMEMBER key [count] 从集合中获取一个或多个随机成员
11 SREM key member1 [member2] 从集合中删除一个或多个成员
12 SUNION key1 [key2] 添加多个集合
13 SUNIONSTORE destination key1 [key2] 添加多个集并将结果集存储在键中
14 SSCAN key cursor [MATCH pattern] [COUNT count] 递增地迭代集合中的元素

参考

Reids设计与实现

--EOF--

Redis数据结构之intset(2)的更多相关文章

  1. Redis数据结构之intset

    本文及后续文章,Redis版本均是v3.2.8 上篇文章<Redis数据结构之robj>,我们说到redis object数据结构,其有5中数据类型:OBJ_STRING,OBJ_LIST ...

  2. Redis 数据结构的底层实现 (二) dict skiplist intset

    一.REDIS_INCODING_HT (dict字典,hashtable) dict是一个用于维护key和value映射关系的数据结构.redis的一个database中所有的key到value的映 ...

  3. Redis数据结构底层知识总结

    Redis数据结构底层总结 本篇文章是基于作者黄建宏写的书Redis设计与实现而做的笔记 数据结构与对象 Redis中数据结构的底层实现包括以下对象: 对象 解释 简单动态字符串 字符串的底层实现 链 ...

  4. Redis 数据结构与内存管理策略(下)

    Redis 数据结构与内存管理策略(下) 标签: Redis Redis数据结构 Redis内存管理策略 Redis数据类型 Redis类型映射 Redis 数据类型特点与使用场景 String.Li ...

  5. Redis数据结构之robj

    本文及后续文章,Redis版本均是v3.2.8 我们知道一个database内的这个映射关系是用一个dict来维护的.dict的key固定用一种数据结构来表达,这这数据结构就是动态字符串sds.而va ...

  6. Redis 数据结构之dict

    上篇文章<Redis数据结构概述>中,了解了常用数据结构.我们知道Redis以高效的方式实现了多种数据结构,因此把Redis看做为数据结构服务器也未尝不可.研究Redis的数据结构和正确. ...

  7. Redis 数据结构的实现

    Redis 数据结构的实现 先看个对照关系: Redis数据结构 实现一 实现二 string 整数(如果value能够表示为整数) 字符串 hash 压缩列表(只包含少量键值对, 并且每个键值对的键 ...

  8. 【Redis】270- 你需要知道的那些 redis 数据结构

    本文出自「掘金社区」,欢迎戳「阅读原文」链接和作者进行技术交流 ?? 作者简介 世宇,一个喜欢吉他.MDD 摄影.自走棋的工程师,属于饿了么上海物流研发部.目前负责的是网格商圈.代理商基础产线,平时喜 ...

  9. 5种Redis数据结构详解

    本文主要和大家分享 5种Redis数据结构详解,希望文中的案例和代码,能帮助到大家. 转载链接:https://www.php.cn/php-weizijiaocheng-388126.html 2. ...

随机推荐

  1. 2.6 datetime 模块

    目录 2.6.1  常用类 2.6.1.1 datetime.date 2.6.1.2 datetime.time 2.6.1.3 datetime.datetime 2.6.1.4 datetime ...

  2. git 忽略部分文件类型的同步

    场景 利用 pycharm 进行代码操作的时候会自动创建 .idea/ 文件夹 特么我每次随便做点操作.这里面的东西也会随着自动改一些 一开始开始无视 如果是多人协同开发会导致代码合并相关的问题 因此 ...

  3. require.js使用教程

    require.js使用教程 下载require.js, 并引入 官网: http://www.requirejs.cn/ github : https://github.com/requirejs/ ...

  4. JavaJDBC整理

    1.1.1    导入驱动jar包 创建lib目录,用于存放当前项目需要的所有jar包 选择jar包,右键执行build path / Add to Build Path 前版本 package co ...

  5. Java NIO系列教程(一) Java NIO 概述

    <I/O模型之四:Java 浅析I/O模型> 一.阻塞IO与非阻塞IO 阻塞IO: 通常在进行同步I/O操作时,如果读取数据,代码会阻塞直至有 可供读取的数据.同样,写入调用将会阻塞直至数 ...

  6. Fiddler--Composer

    Composer选项卡支持手动构建和发请求:也可以在Session列表中拖拽Session放到Composer中,把该Session的请求复制到用户界面: 点击"execute"按 ...

  7. Node.js实战项目学习系列(3) CommonJS 模块化规范

    前言 想开始编写Node.js代码,那么我们就必须先熟悉它的模块化规范CommonJS,本文将详细讲解CommonJS规范 本文代码 >>> github 地址 CommonJS N ...

  8. Shiro 系列 - 基本知识

    和 Spring Security 项目一样, Apache Shiro 也是一个被广泛使用安全框架, 它们都能完成认证.授权.会话管理等. 简单对比一下 Apache Shiro 和 Spring ...

  9. Groovy 设计模式 -- 装饰器模式

    http://groovy-lang.org/design-patterns.html#_chain_of_responsibility_pattern 装饰器模式, 起到美化原始对象的作用. 一个被 ...

  10. 五十二、linux 编程——网络介绍

    52.1 网络介绍 使用远程资源 共享信息.程序和数据 分布处理 52.1.1 协议的概念 计算机网络中实现通信必须有一些约定,如对速率.传输代码.代码结构.传输控制步骤和出错控制等约定,这些约定即被 ...