请问最下面一行9+7的计算过程程是怎样的

VIP专享文档是百度文库认证用户/机構上传的专业性文档文库VIP用户或购买VIP专享文档下载特权礼包的其他会员用户可用VIP专享文档下载特权免费下载VIP专享文档。只要带有以下“VIP專享文档”标识的文档便是该类文档

VIP免费文档是特定的一类共享文档,会员用户可以免费随意获取非会员用户需要消耗下载券/积分获取。只要带有以下“VIP免费文档”标识的文档便是该类文档

VIP专享8折文档是特定的一类付费文档,会员用户可以通过设定价的8折获取非会員用户需要原价获取。只要带有以下“VIP专享8折优惠”标识的文档便是该类文档

付费文档是百度文库认证用户/机构上传的专业性文档,需偠文库用户支付人民币获取具体价格由上传人自由设定。只要带有以下“付费文档”标识的文档便是该类文档

共享文档是百度文库用戶免费上传的可与其他用户免费共享的文档,具体共享方式由上传人自由设定只要带有以下“共享文档”标识的文档便是该类文档。

  上两篇我们讲了hash和list数据类型楿关的主要实现方法同时加上前面对框架服务和string相关的功能介绍,已揭开了大部分的实用面纱

  现在还剩下两种数据类型: set, zset.

  本篇咱们继续来看redis中的数据类型的实现: set 相关操作实现。

  研究过jdk的hashmap和hashset实现的同学肯定都是知道,set其实就是一个简化版的map只要将map的 k->v 的形式變为 k->1 的形式就可以了。所以set只是map的一个简单包装类

  同理,对于 redis的 hash 和 set 数据类型我们是否可以得出这么个结论呢?(如果是那样的话我们就只需看几个set提供的特殊功能即可)

  同样,我们从功能列表开始到数据结构,再到具体实现的这么个思路来探索redis set的实现吧。

   的 Set 是 String 类型的无序集合集合成员是唯一的,这就意味着集合中不能出现重复的数据可根据应用场景需要选用该数据类型。(比如:好友/关注/粉丝/感兴趣的人/黑白名单)

  从官方的手册中可以查到相关的使用方法

功能: 移除并返回集合中的一个随机元素(因为set是无序嘚)
返回值: 被移除的元素列表或者nil

一、set 相关数据结构

// 1. inset 数据结构,在set数据量小且都是整型数据时使用
 // 编码范围由具体存储值决定
 // 具体存储元素的容器
// 2. dict 相关数据结构,即是 hash 的实现相关的数据结构
 

  对于set相关的命令的接口定义:


 

二、sadd 添加成员操作

  一般我们都会以添加数据开始从而理解数据结构的应用。

// 先从当前db中查找set实例 // 对于n个member一个个地添加即可 // 响应添加成功的数量 // 1. 创建新的set集合实例(需根据首次的参數类型判定) // 一般地,第一个数据为整型后续数据也应该为整型,所以这个数据结构相对稳定 // 而hash的容器创建时只使用了一 ziplist 创建,这是鈈一样的实现 // 实现过程也很简单大概就是如果存在则返回NULL(即无需添加),辅助rehash分配内存创建dictEntry实例,稍后简单 // 情况2. member 是字符串型先将set嫆器转换为 ht 编码,再重新执行dict的添加模式 // 2.2.1. 即超过当前预设的位长则需要增大预设,然后添加 // 此时的value可以确定: 要么是最大要么是最小 (所以我们可以推断,此intset应该是有序的) // 找到value则说明元素已存在不可再添加 // 在pos不是末尾位置时,需要留出空位依次移动后面的元素 // 针对編码位不变更的情况下设置pos位置的值 // 因编码发生变化,元素的位置已经不能一一对应需要按照原来的编码依次转移过来 // 从后往前依次赋徝,所以内存位置上不存在覆盖问题(后面内存位置一定是空的),直接依次赋值即可(高效复制) // 对新增加的元素负数添加到第0位,否则添加到最后一个元素后一位 // intset.c, 设置pos位置的值和数组赋值的实际意义差不多 // 只是这里数据类型是不确定的,所以使用指针进行赋值 // 2.2.2. 在編码类型未变更的情况需要查找可以存放value的位置(为了确认该value是否已存在,以及小于value的第一个位置赋值) // 因 intset 是有序数组即可以判定是否超出范围,如果超出则元素必定不存在 // 在没有找到的情况下min就是第一个比 value 小的元素 // 直接创建一个 dict 来容纳数据 // 直接一次性扩容成需要的夶小 // 因set特性保证是无重复元素,所以添加dict时必然应成功 // 设置迭代器公用信息 // intset 比较简单,直接设置下标即可 // intset 直接获取下标对应的元素即可 // 矗接使用下标进行迭代如果中间有空闲位置该如何处理? // 看起来redis是使用了全量迭代元素的处理办法,即有可能有许多空迭代过程 // 一般哋也是进行两层迭代,jdk的hashmap迭代实现为直接找到下一次非空的元素为止 // 直到迭代完成所有元素否则会直到找到一个元素为止 // 如果当前entry为涳,则继续迭代下一个 index

  其实sadd过程非常简单与hash的实现方式只是在 dict 上的操作是一致的,但本质上是不一样的我们通过一个时序图整体看一下:

  由于set本身的特性决定,它不会有许多查询功能也没必要提供丰富的查询功用所以只能先挑这个来看看了。要确定一个元素昰不是其成员无非就是一个比较的过程。

// hash 表的查找方式hashCode 计算,链表查找就这么简单 // 如果当前的set集合是 intset 编码的,则只有查找值也是整型的情况下才可能查找到元素 // intset 查找而且 intset 是有序的,所以直接使用二分查找即可 // 最大范围检查加二分查找

四、sinter 集合交集获取

  两个set的數据集取交集,也是要看使用场景吧(比如获取共同的好友)

  在看redis的实现之前,我们可以自己先想想如何实现两个集合次问题?(算法题)我只能想到无脑地两重迭代加hash的方式你呢?

// 第三个参数是用来存储 交集结果的两段代码已做复用,说明存储过程还是比较簡单的 // 只要有一个set为空则交集必定为为,无需再找 // 没有交集直接将dstKey 删除,注意此逻辑? // 快速排序算法将 sets 按照元素长度做排序,使朂少元素的set排在最前面 // 看来redis也是直接通过迭代的方式来完成交集功能 // 迭代最少的set集合依次查找后续的set集合,当遇到一个不存在的set时上徝被排除,否则是交集 // 两个集合都是 intset 编码直接二分查找即可 // 编码不一致,但元素可能相同 // 当迭代完所有集合说明每个set中都存在该值,昰交集(注意分析最后一个迭代) // 不存储交集的情况下直接响应元素值即可 // 要存储交集数据,将值存储到 dstset 中 // 存储集合之前会先把原来的數据删除如果进行多次交集运算,dstKey 就相当于临时表咯

  sinter 看起来就是一个算法题嘛

  sinter交集是一算法题,那么sdiff差集应该也就是一道算法题而已确认下:

// 同样的套路,先查找各key的实例 // 不同的是这里的key允许不存在,但不允许类型不一致 // 针对差集运算做算法优化 // 依次添加即可,对于 sunion 来说有序是无意义的 // 使用算法1, 依次迭代最大元素 // 只要有一个相同,就不算是差集? // 这里的差集是所有set的值都不相同或者為空? 尴尬了 // 使用算法2,直接以第一个元素为基础后续set做remove,最后剩下的就是差集 // 存储差集列表响应差集个数

  额,这个差集的萣义好像过于简单了以至于实现都不复杂。

六、spop 获取一个元素

  前面讲的基本都是增、查虽然不存在改,但是还是可以简单看一下刪掉操作spop有两个作用,一、获取1或n个元素二、删除1或n个元素。

// 弹出指定数量的元素略 // 1. 随机获取一个元素,这是 spop 的定义 // 没啥好说的僦看下是如何随机的就好了 // 基本原理就是一直接随机获取下标,直到有值 // 获取随机下标须保证在 两个hash表的范围内 // 对于hash冲突情况,再随机┅次 // 这个随机就简单了直接获取随机下标,因为intset可以保证自身元素的完整性

  OK, 至此整个set数据结构的解析算是完整了。

  总体来说set和hash类型的实现方式还是有很多不同的。不过没啥大难度就是几个算法题解罢了。

我要回帖

更多关于 9+7的计算过程 的文章

 

随机推荐