【Redis】set 类型

set

一. set 类型介绍

  • 集合就是把一些有关联的数据放到一起,保存多个字符串类型的元素的 (可以使用 JSON 这样的格式,让 string 存储结构化数据),但和列表类型不同的是:
    • 元素之间是无序的。
      • 此处说的无序和之前 list 说的有序是对应的。
      • 有序:顺序很重要,变换一下顺序,就是不同的列表。
      • 无序:顺序不重要,变换一下顺序,还是那个集合。
      • list:[1, 2, 3] 和 [2, 1, 3] 是不同的 list
      • set:[1, 2, 3] 和 [2, 1, 3] 是相同的 set
    • 元素不允许重复。
    • 一个集合中最多可以存储 2^32 - 1 个元素。
  • Redis 除了支持集合内的增删查改操作,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型,能在实际开发中解决很多问题。

集合类型:

二. set 命令

在 set 中的元素叫做 member,就像在 hash 中的元素叫做 field、value 类似。

sadd、smembers、sismember

  • sadd:添加⼀/多个元素到 set 中。注意:重复的元素无法添加到集合中。
  • 语法:sadd key member [member ...]
  • 时间复杂度:添加一个元素是 O(1),添加 N 个元素是 O(N)
  • 返回值:本次添加成功的元素个数。
  • smembers:获取一个 set 中的所有元素。注意:元素间的顺序是无序的。
  • 语法:smembers key
  • 时间复杂度:O(N),N 是集合中元素的个数。
  • 返回值:所有元素的列表。
  • sismember:判断一个元素在不在 set 中。
  • 语法:sismember key member
  • 时间复杂度:O(1)
  • 返回值:元素在 set 中,返回 1;元素不在 set 中或者 key 不存在,返回 0

scard、spop、srandmember

  • scard:获取一个 set 的基数 (cardinality),即 set 中的元素个数。
  • 语法:scard key
  • 时间复杂度:O(1)
  • 返回值:set 内的元素个数。
  • spop:从 set 中删除并返回一个或者多个元素。注意:由于 set 内的元素是无序的,所以取出哪个元素实际是未定义行为,即可以看作随机的。
  • 语法:spop key [count]
  • 时间复杂度:O(N),N 是 count 的个数。
  • 返回值:取出的元素。
  • srandmember:从 set 中返回一个或者多个随机的元素。
  • 语法:srandmember key [count]
  • 时间复杂度:O(N),N 是 count 的个数。
  • 返回值:取出的元素。

在 Redis 源码中,针对 spop 实现的时候,就采取了 "生成随机数" srandmember 的方式。

smove、srem

  • smove:将一个元素从源 set 取出并放入目标 set 中。
  • 语法:smove source destination member
  • 时间复杂度:O(1)
  • 返回值:移动成功时,返回 1;移动失败时,返回 0

如果我给 key1 里再添加一个 1,再次把这个 1 移动给 key2,此时 smove 不会视为出错,也会按照 删除-插入 进行执行。

  • srem:将指定的元素从 set 中删除
  • 语法:srem key member [member ...]
  • 时间复杂度:O(N),N 是要删除的元素个数。
  • 返回值:删除成功的元素个数。

不同操作的返回值,含义差别还是挺大的,需要用的时候,多翻文档即可。

集合间操作

  • 交集 (inter):最终结果同时出现在两个集合中。
  • 并集 (union):把多个集合中的数据都集中放在一起,如果元素有重复,也最终只保留一份。
  • 差集 (diff):A 和 B 做差集,就是找出 A 中存在,但是 B 中不存在的元素。

交集:sinter、sinterstore

  • sinter:获取给定 set 的交集中的元素。
  • 语法:sinter key [key ...]
  • 时间复杂度:O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数。
  • 返回值:交集的元素。
  • sinterstore:获取给定 set 的交集中的元素并保存到目标 set 中。
  • 语法:sinterstore destination key [key ...]
  • 时间复杂度:O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数。
  • 返回值:交集的元素个数。

并集:sunion、sunionstore

  • sunion:获取给定 set 的并集中的元素。
  • 语法:sunion key [key ...]
  • 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
  • 返回值:并集的元素。
  • sunionstore:获取给定 set 的并集中的元素并保存到目标 set 中。
  • 语法:sunionstore destination key [key ...]
  • 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
  • 返回值:并集的元素个数。

差集:sdiff、sdiffstore

  • sdiff:获取给定 set 的差集中的元素。
  • 语法:sdiff key [key ...]
  • 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
  • 返回值:差集的元素。
  • sdiffstore:获取给定 set 的差集中的元素并保存到目标 set 中。
  • 语法:sdiffstore destination key [key ...]
  • 时间复杂度:O(N),N 是给定的所有集合的总的元素个数。
  • 返回值:差集的元素个数。

三. set 命令小结

命令 执行效果 时间复杂度
sadd key member [member ...] 向集合中添加元素 O(K),K 是元素的个数
smembers key 求集合中的元素 O(K),K 是元素的个数
sismember key member 判断元素是否在集合中 O(1)
scard key 获取集合中元素的个数 O(1)
spop key [count] 随机删除 count 个元素 O(N),N 是 count
srandmember key [count] 随机删除 count 个元素 O(N),N 是 count
smove source destination member 移动源集合中的一个元素到目标集合 O(1)
srem key member [member ...] 删除集合中的元素 O(K),K 是元素的个数
sinter key [key ...] 获取集合的交集 O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数
sinterstore destination key [key ...] 存储集合的交集到目标集合中 O(N * M),N 是最小的集合元素个数,M 是最大的集合元素个数
sunion key [key ...] 获取集合的并集 O(N),N 是所有集合的元素个数
sunionstore destination key [key ...] 存储集合的并集到目标集合中 O(N),N 是所有集合的元素个数
sdiff key [key ...] 获取集合的差集 O(N),N 是所有集合的元素个数
sdiffstore destination key [key ...] 存储集合的差集到目标集合中 O(N),N 是所有集合的元素个数

四. set 内部编码方式

集合类型的内部编码有两种:

  • intset (整数集合):当集合中的元素都是整数并且元素的个数小于 set-max-intset-entries 配置 (默认 512 个) 时,Redis 会选用 intset 来作为集合的内部实现,从而减少内存的使用。
    • Redis 是内存数据库,当元素均为整数,并且元素的个数不是很多的时候,为了节省空间,做出的特点优化。
  • hashtable (哈希表):当集合类型无法满足 intset 的条件时,Redis 会使用 hashtable 作为集合的内部实现。

使用 object encoding key 可以查看集合内部的编码方式,如下:

五. set 使用场景

用户标签

集合类型比较典型的使用场景是标签 (tag),使用 set 来保存用户的 "标签"

  • 例如 A 用户对娱乐、体育板块比较感兴趣,B 用户对历史、新闻比较感兴趣,这些兴趣点可以被抽象为标签。例如一个电子商务网站会对不同标签的用户做不同的产品推荐。
  • 有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于增强用户体验和用户黏度都非常有帮助。分析出你这个人的一些特征,分析清楚之后,再投其所好。
  • 上述用户数据,很多公司都在共享。
    • 两个程序,两个账号,如何知道这两个账号是一个人?
    • 现在的程序登入,主要就是两个入口,手机号、微信。
  • 通过上述过程,搜集到的用户特征,就会转换成 "标签" (简短的字符串),此时就可以把标签保存到 Redis 的 set 中。
    • 用户画像,这种事情其实是挺复杂的事情,一般一个大厂都会有专门的团队做这样工作。
    • 上述玩法,抖音玩的是最好的,其它互联网大厂一看这么搞真好,于是纷纷效仿。
    • 但是存在 "信息茧房" 问题:你看到的内容始终就是一个小圈子,看不到其它圈子中的事物。
    • 当你很认真的看了一个视频之后,人家的服务器就判定你,非常爱看这种类型的视频,接下来就给你疯狂推送。

下面的演示通过集合类型来实现标签的若干功能。

  • 给用户添加标签
cpp 复制代码
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
  • 给标签添加用户
cpp 复制代码
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:4 user:9 user:28
  • 删除用户下的标签
cpp 复制代码
srem user:1:tags tag1 tag5
...
  • 删除标签下的用户
cpp 复制代码
srem tag1:users user:1
srem tag5:users user:1
...
  • 计算用户的共同兴趣标签
cpp 复制代码
sinter user:1:tags user:2:tags

共同好友

使用 set 来计算用户之间的公共好友,基于 "集合求交集"

  • 例如:QQ,我这边加了很多好友,你那边也加了很多好友。
  • 基于上述还可以做一些好友推荐:A 和 B 是好友,A 和 C 是好友,B 和 C 和 D 都是好友,此时系统就会把 D 推荐给 A

统计 UV

一个互联网产品,如何衡量用户量,用户规模?主要的指标,是两方面:

  • PV (page view):用户每次访问该服务器,都会产生一个 PV
  • UV (user view):每个用户访问该服务器,都会产生一个 UV,但是同一个用户多次访问,不会增加 UV 的个数。
    • UV 需要按照用户进行去重,上述的去重功能,就可以使用 set 来实现。
相关推荐
伍六星4 分钟前
更新Java的环境变量后VScode/cursor里面还是之前的环境变量
java·开发语言·vscode
风象南10 分钟前
SpringBoot实现简易直播
java·spring boot·后端
万能程序员-传康Kk19 分钟前
智能教育个性化学习平台-java
java·开发语言·学习
落笔画忧愁e29 分钟前
扣子Coze飞书多维表插件-列出全部数据表
java·服务器·飞书
秃了也弱了。31 分钟前
DBSyncer:开源数据库同步利器,MySQL/Oracle/ES/SqlServer/PG/
数据库·mysql·开源
鱼儿也有烦恼31 分钟前
Elasticsearch最新入门教程
java·elasticsearch·kibana
eternal__day41 分钟前
微服务架构下的服务注册与发现:Eureka 深度解析
java·spring cloud·微服务·eureka·架构·maven
玄辰星君1 小时前
PostgreSQL 入门教程
数据库·postgresql
一介草民丶1 小时前
Jenkins | Linux环境部署Jenkins与部署java项目
java·linux·jenkins
武子康1 小时前
Java-39 深入浅出 Spring - AOP切面增强 核心概念 通知类型 XML+注解方式 附代码
xml·java·大数据·开发语言·后端·spring