Redis数据类型-集合对象
- 集合对象
- 集合对象的编码转换
- 集合对象的命令(包括不同编码情况下的实现方法)
- 集合对象相关命令
- 集合内操作
- 1.添加元素-sadd
- 2.删除元素-srem
- 3.将元素从一个集合移动到另一个集合-smove
- 4.计算元素个数-scard
- 5.判断元素是否在集合中-sismember
- 6.随机从集合内 返回 指定个数 元素-srandmember
- 7.从集合内随机弹出(删除)元素-spop
- 8.获取所有元素-smembers
- 集合间操作
- 1.求多个集合的交集-sinter
- 2.求多个集合的并集-suinon
- 3.求多个集合的差集-sdiff
- 4.将交集、并集、差集的结果保存
- 时间复杂度
- 集合对象的使用场景(重要!!!)
- 总结(重要!!!)
集合对象
集合(set)类型也是用来保存多个的字符串元素
但和列表类型不一样的是,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素
集合和列表这两个区别带来的差异主要跟命令的复杂度有关:
- 在执行像linsert和lrem这样的列表命令时,即使命令只针对单个列表元素,程序有时也不得不遍历整个列表以确定指定的元素是否存在,因此这些命令的复杂度都为O(N)
- 对于集合来说,因为所有针对单个元素的集合命令都不需要遍历整个集合,所以复杂度都为O(1)
因此当需要存储多个元素时,就可以考虑这些元素是否可以以无序的方式存储,并且是否不会出现重复,如果是,那么就可以使用集合来存储这些元素,从而有效地利用集合操作的效率优势
一个集合最多可以存储232-1个元素
Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,合理地使用好集合类型
集合对象的编码可以是intset
或者hashtable
阅读参考:Redis数据结构(五)-整数集合(intset)intset
编码 的 集合对象 使用 整数集合 作为 底层实现,集合对象 包含的所有元素 都被保存在 整数集合里面
举个例子,以下代码将创建一个intset
编码集合对象
redis> SADD numbers 1 3 5
(integer) 3
另一方面,hashtable
编码的集合对象使用字典作为底层实现,字典的每个键 都是一个 字符串对象,每个字符串对象 包含了一个 集合元素,而字典的值 则 全部被设置为 NULL
举个例子,以下代码将创建一个hashtable编码集合对象
集合对象的编码转换
集合对象可以同时满足以下两个条件时,对象使用intset
编码
- 集合对象保存的所有元素都是整数值
- 集合对象保存的元素数量不超过512个
不能满足这两个条件的集合对象需要使用hashtable
编码
注意:
第二个条件的上限值是可以修改的,具体请看配置文件中关于set-max-intset-entries
选项的说明
对于使用intset
编码的集合对象来说,当使用intset
编码所需的两个条件的任意一个不能被满足时,就会执行对象的编码转换操作,原本保存在整数集合中的所有元素都会被转移并保存到字典里面,并且对象的编码也会从intset
变为hashtable
举个例子,以下代码创建了一个只包含整数元素的集合对象,该对象的编码为intset
redis> SADD numbers 1 3 5
(integer) 3
redis> OBJECT ENCODING numbers
"intset"
不过,只要向这个只包含整数元素的集合对象添加一个字符串元素,集合对象的编码转移操作就会被执行
redis> SADD numbers "seven"
integer) 1
redis> OBJECT ENCODING numbers
"hashtable"
除此之外,如果创建一个包含512个整数元素的集合对象,那么对象的编码应该会是intset
redis> EVAL "for i=1, 512 do redis.call('SADD', KEYS[1], i) end" 1 integers
(nil)
redis> SCARD integers
(integer) 512
redis> OBJECT ENCODING integers
"intset"
但是,只要再向集合添加一个新的整数元素,使得这个集合的元素数量变成513,那么对象的编码转换操作就会被执行
redis> SADD integers 10086
(integer) 1
redis> SCARD integers
(integer) 513
redis> OBJECT ENCODING integers
"hashtable"
集合对象的命令(包括不同编码情况下的实现方法)
集合对象相关命令
集合内操作
1.添加元素-sadd
在使用sadd命令向集合中添加元素的时候,sadd命令会自动忽略已存在的元素,只将不存在于集合的新元素添加到集合中
sadd key element [element ...]
返回结果为添加成功的元素个数,例如
127.0.0.1:6379> exists myset
(integer) 0
127.0.0.1:6379> sadd myset a b c
(integer) 3
127.0.0.1:6379> sadd myset a b
(integer) 0
2.删除元素-srem
如果给定的元素并不存在于集合当中,那么srem命令将忽略不存在的元素,只移除那些确实存在的元素
srem key element [element ...]
返回结果为成功删除元素个数,例如
127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> srem myset hello
(integer) 0
3.将元素从一个集合移动到另一个集合-smove
smove命令允许用户将指定的元素从源集合移动到目标集合:
smove source target element
smove命令在移动操作成功执行时返回1
如果指定的元素并不存在于源集合,那么smove命令将返回0,表示移动操作执行失败
即使想要移动的元素已经存在于目标集合,smove命令仍然会将指定的元素从源集合移动到目标集合,并覆盖目标集合中的相同元素
从结果来看,这种移动不会改变目标集合包含的元素,只会导致被移动的元素从源集合中消失
4.计算元素个数-scard
scard key
scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量
127.0.0.1:6379> scard myset
(integer) 1
5.判断元素是否在集合中-sismember
sismember key element
如果给定元素element在集合内返回1,反之返回0,例如
127.0.0.1:6379> sismember myset c
(integer) 1
6.随机从集合内 返回 指定个数 元素-srandmember
当count参数的值大于集合包含的元素数量时,srandmember命令将返回集合包含的所有元素
如果count参数的值为负数,那么srandmember命令将随机返回abs(count)个元素,并且在这些元素当中允许出现重复的元素
srandmember key [count]
[count]是可选参数,如果不写默认为1,例如
127.0.0.1:6379> srandmember myset 2
1) "a"
2) "c"
127.0.0.1:6379> srandmember myset
"d"
7.从集合内随机弹出(删除)元素-spop
spop key
spop操作可以从集合中随机弹出一个元素,例如下面代码是一次spop后,集合元素变为"d b a"
127.0.0.1:6379> spop myset
"c"
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
需要注意的是Redis从3.2版本开始,spop也支持[count]参数,默认是1srandmember
和spop
都是随机从集合选出元素,两者不同的是spop
命令执行后,元素会从集合中删除,而srandmember
不会
8.获取所有元素-smembers
因为Redis集合以无序的方式存储元素,并且smembers 命令在获取集合元素时也不会对元素进行任何排序动作,所以根据元素添加顺序的不同,2个包含相同元素的集合在执行smembers 命令时的结果也可能会有所不同
smembers key
下面代码获取集合myset所有元素,并且返回结果是无序的
127.0.0.1:6379> smembers myset
1) "d"
2) "b"
3) "a"
smembers
和lrange
、hgetall
都属于比较重的命令,如果元素过多存在阻塞Redis的可能性,这时候可以使用sscan
来完成
集合间操作
现在有两个集合,它们分别是user:1:follow 和 user:2:follow
127.0.0.1:6379> sadd user:1:follow it music his sports
(integer) 4
127.0.0.1:6379> sadd user:2:follow it news ent sports
(integer) 4
1.求多个集合的交集-sinter
sinter key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的交集,返回结果是sports、it
127.0.0.1:6379> sinter user:1:follow user:2:follow
1) "sports"
2) "it"
2.求多个集合的并集-suinon
suinon key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的并集,返回结果是sports、it、his、news、music、ent
127.0.0.1:6379> sunion user:1:follow user:2:follow
1) "sports"
2) "it"
3) "his"
4) "news"
5) "music"
6) "ent"
3.求多个集合的差集-sdiff
sdiff key [key ...]
例如下面代码是求user:1:follow和user:2:follow两个集合的差集,返回结果是music和his
127.0.0.1:6379> sdiff user:1:follow user:2:follow
1) "music"
2) "his"
4.将交集、并集、差集的结果保存
sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...]
集合间的运算在元素较多的情况下会比较耗时,所以Redis提供了上面三个命令(原命令+store)将集合间交集、并集、差集的结果保存在destination key中
例如下面操作将user:1:follow和user:2:follow两个集合的交集结果保存在user:1_2:inter中,user:1_2:inter本身也是集合类型
127.0.0.1:6379> sinterstore user:1_2:inter user:1:follow user:2:follow
(integer) 2
127.0.0.1:6379> type user:1_2:inter
set
127.0.0.1:6379> smembers user:1_2:inter
1) "it"
2) "sports"
时间复杂度
集合对象的使用场景(重要!!!)
集合类型比较典型的使用场景是标签(tag)
例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签
有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要
例如一个电子商务的网站会对不同标签的用户做不同类型的推荐,比如对数码产品比较感兴趣的人,在各个页面或者通过邮件的形式给他们推荐最新的数码产品,通常会为网站带来更多的利益
下面使用集合类型实现标签功能的若干功能
(1)给用户添加标签
sadd user:1:tags tag1 tag2 tag5
sadd user:2:tags tag2 tag3 tag5
...
sadd user:k:tags tag1 tag2 tag4
...
(2)给标签添加用户
sadd tag1:users user:1 user:3
sadd tag2:users user:1 user:2 user:3
...
sadd tagk:users user:1 user:2
...
(3)删除用户下的标签
srem user:1:tags tag1 tag5
...
(4)删除标签下的用户
srem tag1:users user:1
srem tag5:users user:1
...
(3)计算用户共同感兴趣的标签
可以使用sinter
命令,来计算用户共同感兴趣的标签,如下代码所示
sinter user:1:tags user:2:tags
总结(重要!!!)
集合类型的应用场景通常为以下几种
- sadd=Tagging(标签)
- spop/srandmember=Random item(生成随机数,比如抽奖)
- sadd+sinter=Social Graph(社交需求)