3.5 集合类型
3.5.1 介绍
集合的概念高中的数学课就学习过。在集合中的每个元素都是不同的,且没有顺序。一个集合类型(set
)键可以存储至多232-1个(相信这个数字对大家来说已经很熟悉了)字符串。集合类型和列表类型有相似之处,但很容易将它们区分开来,如表3-4所示。
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是0(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算,稍后就会看到灵活
运用这一特性带来的便利。
3.5.2 命令
1.增加/删除元素
SADD key member [member …]
SREM key member [member …]
SADD
命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。本命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。例如:
redis>SADD letters a (integer) 1 redis> SADD letters a b c (integer) 2
第二条SADD命令的返回值为2是因为元素“a”已经存在,所以实际上只加入了两个元素。
SREM
命令用来从集合中删除一个或多个元素,并返回删除成功的个数,例如:
redis>SREM letters c d (integer) 1
由于元素“d”在集合中不存在,所以只删除了一个元素,返回值为1。
2.获得集合中的所有元素
SMEMBERS key
SMEMBERS
命令会返回集合中的所有元素,例如:
redis>SMEMBERS letters 1) "b" 2) "a"
3.判断元素是否在集合中
SISMEMBER key member
判断一个元素是否在集合中是一个时间复杂度为0(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时SISMEMBER
命令返回1,当值不存在或键不存在时返回0,例如:
redis>SISMEMBER letters a (integer) 1 redis>SISMEMBER letters d (integer) 0
4.集合间运算
SDIFF key [key …]
SINTER key [key …]
SUNION key [key …]
接下来要介绍的3个命令都是用来进行多个集合间运算的。
(1)SDIFF
命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A-B,代表所有属于A且不属于B的元素构成的集合(如图3-13所示),即A-B={x|x∈A且x∈/B}。例如:
{1, 2, 3}-{2, 3, 4}={1}
{2, 3, 4}-{1, 2, 3}={4}
SDIFF命令的使用方法如下:
redis>SADD setA 1 2 3 (integer) 3 redis>SADD setB 2 3 4 (integer) 3 redis>SDIFF setA setB 1) "1" redis>SDIFF setB setA 1 ) "4"
SDIFF 命令支持同时传入多个键,例如:
redis>SADD setC 2 3 (integer) 2 redis>SDIFF setA setB setC 1 ) "1"
计算顺序是先计算setA-setB,再计算结果与setC的差集。
(2)SINTER
命令用来对多个集合执行交集运算。集合A与集合B的交集表示为A∩B,代表所有属于A且属于B的元素构成的集合(如图3-14所示),即A∩B={x|x∈A且x∈B}。例如:
{1, 2, 3}∩{2, 3, 4}={2, 3}
SINTER命令的使用方法如下:
redis>SINTER setA setB 1) "2" 2) "3"
SINTER命令同样支持同时传入多个键,如:
redis>SINTER setA setB setC 1) "2" 2) "3"
(3)SUNION
命令用来对多个集合执行并集运算。集合A与集合B的并集表示为AUB,代表所有属于A或属于B的元素构成的集合(如图3-15所示),即AUB={x|x∈A 或x∈B}。例如:
{1, 2, 3}∪{2, 3, 4}={1, 2, 3, 4}
SUNION命令的使用方法如下:
redis>SUNION setA setB 1) "1" 2) "2" 3) "3" 4) "4"
SUNION命令同样支持同时传入多个键,例如:
redis>SUNION setA setB setC 1) "1" 2) "2" 3) "3" 4) "4"
3.5.3 命令拾遗
1.获得集合中元素个数
SCARD keySCARD
命令用来获得集合中的元素个数,例如:
redis>SMEMBERS letters 1) "b" 2) "a" redis>SCARD letters (integer) 2
2.进行集合运算并将结果存储
SDIFFSTORE destination key [key …]
SINTERSTORE destination key [key …]
SUNIONSTORE destination key [key …]
SDIFFSTORE
命令和SDIFF命令功能一样,唯一的区别就是前者不会直接返回运算结果,而是将结果存储在destination键中。
SDIFFSTORE
命令常用于需要进行多步集合运算的场景中,如需要先计算差集再将结果和其他键计算交集。
SINTERSTORE
和SUNIONSTORE
命令与之类似,不再赘述。
3.随机获得集合中的元素
SRANDMEMBER key [count]SRANDMEMBER
命令用来随机从集合中获取一个元素,如:
redis>SRANDMEMBER letters "a" redis>SRANDMEMBER letters "b" redis>SRANDMEMBER letters "a"
还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。
(1)当count为正数时,SRANDMEMBER会随机从集合里获得count个不重复的元素。如果count的值大于集合中的元素个数,则SRANDMEMBER会返回集合中的全部元素。
(2)当count为负数时,SRANDMEMBER会随机从集合里获得|count|个的元素,这些元素有可能相同。
为了示例,我们先在letters集合中加入两个元素:
redis>SADD letters c d (integer) 2
目前letters集合中共有“a”、“b”、“c”、“d”4个元素,下面使用不同的参数对SRANDMEMBER命令进行测试:
redis>SRANDMEMBER letters 2 1) "a" 2) "c" redis>SRANDMEMBER letters 2 1) "a" 2) "b" redis>SRANDMEMBER letters 100 1) "b" 2) "a" 3) "c" 4) "d" redis>SRANDMEMBER letters -2 1) "b" 2) "b" redis>SRANDMEMBER letters -10 1) "b" 2) "b" 3) "c" 4) "c" 5) "b" 6) "a" 7) "b" 8) "d" 9) "b" 10) "b"
细心的读者可能会发现SRANDMEMBER命令返回的数据似乎并不是非常的随机,从SRANDMEMBER letters -10这个结果中可以很明显地看出这个问题(b元素出现的次数相对较多① ),出现这种情况是由集合类型采用的存储结构(散列表)造成的。散列表使用散列函数将元素映射到不同的存储位置(桶)上以实现0(1)时间复杂度的元素查找,举个例子,当使用散列表存储元素b时,使用散列函数计算出b的散列值是0,所以将b存入编号为0 的桶(bucket)中,下次要查找b时就可以用同样的散列函数再次计算b的散列值并直接到相应的桶中找到b。当两个不同的元素的散列值相同时会出现冲突,Redis使用拉链法来解决冲突,即将散列值冲突的元素以链表的形式存入同一桶中,查找元素时先找到元素对应的桶,然后再从桶中的链表中找到对应的元素。使用SRANDMEMBER命令从集合中获得一个随机元素时,Redis首先会从所有桶中随机选择一个桶,然后再从桶中的所有元素中随机选择一个元素,所以元素所在的桶中的元素数量越少,其被随机选中的可能性就越大,如图3-19所示。注释:①如果你亲自跟着输入了命令可能会发现得到的结果与书中的结果并不相同,这是正常现象,见后文描述。
图3-19 Redis会先从3个桶中随机挑一个非空的桶,然后再从桶中随机选择一个元素,所以选中元素b的概率会大一些
4.从集合中弹出一个元素
SPOP key
3.4节中我们学习过LPOP命令,作用是从列表左边弹出一个元素(即返回元素的值并删除它)。SPOP
命令的作用与之类似,但由于集合类型的元素是无序的,所以SPOP命令会从集合中随机选择一个元素弹出。例如:
redis>SPOP letters "b" redis>SMEMBERS letters 1) "a" 2) "c" 3) "d"