Redis中的list、set、hash、sorted_set

Posted by 石福鹏 on 2020-03-18
Estimated Reading Time 17 Minutes
Words 3.6k In Total
Viewed Times

一、List链表

list是一个链表,链表分为单向链表,双向链表,它是一个线性的。

既能从前面找到我们想到的元素,也能总后面往前找,这叫双向的

从最后一个能回到第一个,从第一个还能直接到最后一个,这叫有环。一般无环的双向链表,这叫做链表。

Redis的key,包括两个东西,一个是head头指针,一个是tail尾指针,head可以指向到value链表中的第一个元素,tail可以指向到链表的最后一个元素

好处:当value的类型是list的时候,可以通过这两个属性快速的访问value链表中的第一个元素和最后一个元素

image-20210318142604104

通过Redis客户端提供的help,查看相关的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
127.0.0.1:6380> help @list

BLPOP key [key ...] timeout
summary: Remove and get the first element in a list, or block until one is available
since: 2.0.0

BRPOP key [key ...] timeout
summary: Remove and get the last element in a list, or block until one is available
since: 2.0.0

BRPOPLPUSH source destination timeout
summary: Pop an element from a list, push it to another list and return it; or block until one is available
since: 2.2.0

LINDEX key index
summary: Get an element from a list by its index
since: 1.0.0

LINSERT key BEFORE|AFTER pivot element
summary: Insert an element before or after another element in a list
since: 2.2.0

LLEN key
summary: Get the length of a list
since: 1.0.0

LPOP key
summary: Remove and get the first element in a list
since: 1.0.0

LPUSH key element [element ...]
summary: Prepend one or multiple elements to a list
since: 1.0.0

LPUSHX key element [element ...]
summary: Prepend an element to a list, only if the list exists
since: 2.2.0

LRANGE key start stop
summary: Get a range of elements from a list
since: 1.0.0

LREM key count element
summary: Remove elements from a list
since: 1.0.0

LSET key index element
summary: Set the value of an element in a list by its index
since: 1.0.0

LTRIM key start stop
summary: Trim a list to the specified range
since: 1.0.0
RPOP key
summary: Remove and get the last element in a list
since: 1.0.0

RPOPLPUSH source destination
summary: Remove the last element in a list, prepend it to another list and return it
since: 1.2.0

RPUSH key element [element ...]
summary: Append one or multiple elements to a list
since: 1.0.0

RPUSHX key element [element ...]
summary: Append an element to a list, only if the list exists
since: 2.2.0

127.0.0.1:6380>

1、lpushrpushlpopLRANGE

lpush从左边开往里面添加元素

1
2
3
127.0.0.1:6380> lpush k1 a b c d e f 
6
# 存储顺序:f e d c b a

rpush从右边追加元素

1
2
3
127.0.0.1:6380> rpush k2 a b c d e f 
6
# 存储顺序:a b c d e f

lpop,从左边弹出一个元素

1
2
3
4
5
127.0.0.1:6380> lpop k1
f
127.0.0.1:6380> lpop k1
e
127.0.0.1:6380>

同向的命令描述的是Java中的栈

1
2
3
4
5
127.0.0.1:6380> rpop k2
f
127.0.0.1:6380> rpop k2
e
127.0.0.1:6380>

list还能描述队列:反向命令描述的是Java中的队列,先进先出

LRANGE获取所有的元素

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6380> LRANGE k1 0 -1
d
c
b
a
127.0.0.1:6380> LRANGE k2 0 -1
a
b
c
d
127.0.0.1:6380>

lindex 根据索引去除元素

1
2
3
4
5
127.0.0.1:6380> lindex k1 2
b
127.0.0.1:6380> lindex k1 -1
a
127.0.0.1:6380>

lset 根据索引给某个索引位置的元素赋值

1
2
3
4
5
6
7
8
9
127.0.0.1:6380> lset k1 2 aaaa
OK
127.0.0.1:6380> LRANGE k1 0 -1
d
c
aaaa
a
127.0.0.1:6380>

以上的这些操作,又向我们使用的数组,使用下标操作

lrem,移除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
127.0.0.1:6380> lpush k3 1 a 2 b 3 a 4 c 5 a 6 d 
12
127.0.0.1:6380> LRANGE k3 0 -1
d
6
a
5
c
4
a
3
b
2
a
1
127.0.0.1:6380> LREM k3 2 a # 移除两个a
2
127.0.0.1:6380> LRANGE k3 0 -1
d
6
5
c
4
3
b
2
a
1
127.0.0.1:6380>

我们先把刚remove的插回去linsert

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6380> linsert k3 after 6 a  # 在6的后面插入一个a ,如果有两个6 ,就会在第一个后面追加
11
127.0.0.1:6380> linsert k3 before 3 a # 在3的前面插入一个a
12
127.0.0.1:6380> LRANGE k3 0 -1
d
6
a
5
c
4
a
3
b
2
a
1
127.0.0.1:6380>

继续使用lrem从后面开始移除两个a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6380> lrem k3 -2 a
2
127.0.0.1:6380> LRANGE k3 0 -1
d
6
a
5
c
4
3
b
2
1
127.0.0.1:6380>

llen统计元素的个数

1
2
3
127.0.0.1:6380> LLEN k3
10
127.0.0.1:6380>

2、blpopblpushbrpopbrpush

阻塞弹出元素,同时开三个客户端,都连接同一个server,client01、client02分别执行

1
blpop k5  0 

因为redis中都没有k5这个key,所以都会是阻塞状态,client03执行rpush k5 hello,会发现client01弹出了元素,但是client02依然阻塞

,在client03中再次rpush k5 world,发现client02也弹出了元素

因此,它其实还支持一个简单的阻塞队列,或者叫阻塞的单播队列

什么叫阻塞的单播队列?

先进先出,redis中有一个特征,后面会说到

3、ltrim

给出一个start、stop,然后对list中两端的数据进行删除,中间的不会删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
127.0.0.1:6380> lpush k6  a b fd qwe 12d sdf 

6
127.0.0.1:6380> LRANGE k6 0 -1
sdf
12d
qwe
fd
b
a
127.0.0.1:6380> LTRIM k6 0 -1
OK
127.0.0.1:6380> LRANGE k6 0 -1
sdf
12d
qwe
fd
b
a
127.0.0.1:6380> LTRIM k6 1 -2
OK
127.0.0.1:6380> LRANGE k6 0 -1
12d
qwe
fd
b
127.0.0.1:6380>

二、hash

说到hash,我们想到Java中的hashmap,map,那么一定是有键值对的

这里说的是,redis键值对中,value的类型是hash,hash代表着自身里面又是一个键值对

在没有学习hash之前,我们要想存一个用户的信息,姓名、年龄、地址等,我们可以那字符串按照一些字符拼接,或者按照某个属性一个兼职对进行存取,但是这个成本很廉价,属性很多的时候,取出来要么需要拆分遍历,要么直接遍历(通过keys steven*取回所有)

这就引入了hash,hash一般都是以h开头,基本上都是在字符串的命令前加了H

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
127.0.0.1:6380> hset steven name 石福鹏  # hset
1
127.0.0.1:6380> hset steven age 18
1
127.0.0.1:6380> hset steven address china
1
127.0.0.1:6380> hget steven name # hget
石福鹏
127.0.0.1:6380> hget steven age
18
127.0.0.1:6380> HMGET steven name age address # HMGET
石福鹏
18
china
127.0.0.1:6380> hkeys steven # hkeys
name
age
address
127.0.0.1:6380> HVALS steven # HVALS
石福鹏
18
china
127.0.0.1:6380> HGETALL steven # HGETALL
name
石福鹏
age
18
address
china
127.0.0.1:6380>

也支持数值计算

1
2
3
4
5
6
7
8
127.0.0.1:6380> HINCRBYFLOAT steven age 0.5  # HINCRBYFLOAT 
18.5
127.0.0.1:6380> hget steven age
18.5
127.0.0.1:6380> HINCRBYFLOAT steven age -1 # 没有减,但是加法也可以做减法
17.5
127.0.0.1:6380>

1、hash应用场景

点赞、收藏、详情页等等,面向一个人或者一个事物的页面等场景

三、set 集合操作(用的比较多)

list是可以重复出现的,并且是有序的(注意这个序只是存入的顺序,不会做排序)

set是去重的,里面不维护排序,也不维护插入和弹出的顺序,完全是乱序的

优点: 去重

1、set的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
127.0.0.1:6380> help @set

SADD key member [member ...]
summary: Add one or more members to a set
since: 1.0.0

SCARD key
summary: Get the number of members in a set
since: 1.0.0

SDIFF key [key ...]
summary: Subtract multiple sets
since: 1.0.0

SDIFFSTORE destination key [key ...]
summary: Subtract multiple sets and store the resulting set in a key
since: 1.0.0

SINTER key [key ...]
summary: Intersect multiple sets
since: 1.0.0

SINTERSTORE destination key [key ...]
summary: Intersect multiple sets and store the resulting set in a key
since: 1.0.0

SISMEMBER key member
summary: Determine if a given value is a member of a set
since: 1.0.0

SMEMBERS key
summary: Get all the members in a set
since: 1.0.0

SMOVE source destination member
summary: Move a member from one set to another
since: 1.0.0

SPOP key [count]
summary: Remove and return one or multiple random members from a set
since: 1.0.0

SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0

SREM key member [member ...]
summary: Remove one or more members from a set
since: 1.0.0

SSCAN key cursor [MATCH pattern] [COUNT count]
summary: Incrementally iterate Set elements
since: 2.8.0

SUNION key [key ...]
summary: Add multiple sets
since: 1.0.0

SUNIONSTORE destination key [key ...]
summary: Add multiple sets and store the resulting set in a key
since: 1.0.0

127.0.0.1:6380>

saddSMEMBERS 添加、查询

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6380> SADD key1 wqe 21 3e3 wqd xxww qwew324 3243 21 3e3
0
127.0.0.1:6380> SMEMBERS key1
wqe
3e3
qwew324
21
wqd
xxww
3243
127.0.0.1:6380>

srem移除

1
2
3
4
5
6
7
8
9
127.0.0.1:6380> srem key1 wqe 3e3
2
127.0.0.1:6380> SMEMBERS key1
qwew324
21
wqd
xxww
3243
127.0.0.1:6380>

求交并差集

SINTER只求交集

1
2
3
4
5
6
7
8
127.0.0.1:6380> sadd key2 11 22 33 44 55 
5
127.0.0.1:6380> sadd key3 11 66 77 44 88
5
127.0.0.1:6380> SINTER key2 key3
11
44
127.0.0.1:6380>

SINTERSTORE将交集存为另一个key,求出交集并将交集sadd到新的key,是在redis的server端完成的,所以不会产生I/O

1
2
3
4
5
6
7
127.0.0.1:6380> SINTERSTORE key_des key2 key3
2
127.0.0.1:6380> SMEMBERS key_des
11
44
127.0.0.1:6380>

SUNION求并集,当然也有SUNIONSTORE,与SINTERSTORE一样的道理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6380> SUNION key2 key3
11
22
33
44
55
66
77
88
127.0.0.1:6380> SUNIONSTORE key_bingji key2 key3
8
127.0.0.1:6380> SMEMBERS key_bingji
11
22
33
44
55
66
77
88
127.0.0.1:6380>

sdiff差集,它是有方向的,需要哪边的差值出来,就把哪个key放在前面

1
2
3
4
5
6
7
8
9
127.0.0.1:6380> SDIFF key2 key3 # 求差值 key2中有的,key3中没有的元素
22
33
55
127.0.0.1:6380> SDIFF key3 key2 # 求差值 key3中有的,key2中没有的元素
66
77
88
127.0.0.1:6380>

2、SRANDMEMBER随机事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
127.0.0.1:6380> FLUSHALL 
OK
127.0.0.1:6380> sadd k1 xxoo ooxx xoxo oxox xoox oxxo
6
127.0.0.1:6380> SMEMBERS k1
ooxx
xoxo
xxoo
oxox
oxxo
xoox
127.0.0.1:6380> SRANDMEMBER k1 5
oxox
oxxo
xxoo
ooxx
xoxo
127.0.0.1:6380>
1
2
3
4
5
6
127.0.0.1:6380> help SRANDMEMBER

SRANDMEMBER key [count]
summary: Get one or multiple random members from a set
since: 1.0.0
group: set

这个命令中的[count]

1、如果count为正数,取出一个去重的结果集(不能超过已有集)

2、如果count为负数,取出带重复元素的结果集,且满足需要的数量

3、如果count为0,不返回

可以解决什么问题呢?

抽奖:一个活动。有三个奖品,但是有很多的粉丝。所以,将粉丝存入数据集,然后从中抽取三个人,这个可以是每个人只能中一个,那么count就是正数;也可以不限制,那count就是-3,这种情况,有可能一个人中奖,。也有可能两个人中奖,也有可能三个人中奖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
127.0.0.1:6380> SRANDMEMBER k1 -20
xxoo
ooxx
oxox
xoox
xoxo
xxoo
xoox
ooxx
xxoo
xoxo
xoxo
ooxx
oxox
xxoo
oxxo
oxxo
oxxo
xxoo
xoox
ooxx
127.0.0.1:6380>

spop这个也适用于抽奖,准确的说更符合一些,也就是中奖的就不能再参与抽奖了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6380> SPOP key1

127.0.0.1:6380> SPOP k1
xoxo
127.0.0.1:6380> SPOP k1
xoox
127.0.0.1:6380> SPOP k1
oxxo
127.0.0.1:6380> SPOP k1
oxox
127.0.0.1:6380> SPOP k1
xxoo
127.0.0.1:6380> SPOP k1
ooxx
127.0.0.1:6380> SPOP k1

127.0.0.1:6380> SPOP k1

3、sorted_set有序集

首先它是一个set,是一个集合,所以是有元素,想让他们排序,如何排序

zadd添加元素,每个元素是带score分值的

1
2
3
4
5
6
127.0.0.1:6380> help zadd
key 存在再设置
ZADD key [NX|XX] [CH] [INCR] score member [score member ...]
summary: Add one or more members to a sorted set, or update its score if it already exists
since: 1.2.0
group: sorted_set

存储后的结果,物理内存中是左小右大

1
2
3
4
5
6
7
127.0.0.1:6380> ZADD k2 8 apple 2 banana 3 orange
3
127.0.0.1:6380> ZRANGE k2 0 -1
banana
orange
apple
127.0.0.1:6380>

也可以在结果中带分值ZRANGE k2 0 -1 withscores

ZRANGEBYSCORE按照分值取

1
2
3
4
127.0.0.1:6380> ZRANGEBYSCORE k2 3 8
orange
apple
127.0.0.1:6380>

ZREVRANGE从高到低取出分值前两个

1
2
3
4
127.0.0.1:6380> ZREVRANGE k2 0 1
apple
orange
127.0.0.1:6380>

ZSCORE通过元素取分值

1
2
3
127.0.0.1:6380> ZSCORE k2 apple
8
127.0.0.1:6380>

ZRANK取出排名

1
2
3
4
127.0.0.1:6380> ZRANK k2 apple
2
127.0.0.1:6380>

ZINCRBY增加分值,并且还会根据新的分值排序

1
2
3
4
5
6
7
8
9
10
11
127.0.0.1:6380> ZINCRBY k2 2.5 banana
4.5
127.0.0.1:6380> ZRANGE k2 0 -1 withscores
orange
3
banana
4.5
apple
8
127.0.0.1:6380>

歌曲排行版:播放量、点播量、下载数等,初始时分值都是0,每个有变化,则按照上面的方式+1,会自动进行排序

集合操作 交集、并集、差集等

首先,既然是一个集合,那么是可以做交并差集的,元素可以做,但是分值呢?

分值可以求和,求最小值,求最大值

ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]

ZUNIONSTORE 目标key 偏移量(即几个key参与)k1 k2 权重 分值计算方式(默认sum)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6380> ZADD k3 80 tom 60 sean 70 steven
3
127.0.0.1:6380> ZADD k4 60 tom 100 sean 40 yiming
3
127.0.0.1:6380> ZUNIONSTORE unkey 2 k3 k4
4
127.0.0.1:6380> ZRANGE unkey 0 -1 withscores
yiming
40
steven
70
tom
140
sean
160
127.0.0.1:6380>

加入权重,先分值乘以权重,然后再进行sum

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6380> ZUNIONSTORE unkey 2 k3 k4 weights 1 0.5   # 加入权重 1就是k1的分值乘以1,0.5就k2分值乘以0.5
4
127.0.0.1:6380> ZRANGE unkey 0 -1 withscores
yiming
20
steven
70
sean
110
tom
110
127.0.0.1:6380>

加入分值规则

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6380> ZUNIONSTORE unkey 2 k3 k4  aggregate  max
4
127.0.0.1:6380> ZRANGE unkey 0 -1 withscores
yiming
40
steven
70
tom
80
sean
100
127.0.0.1:6380>

在权重的基础上,加入分值规则

1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6380> ZUNIONSTORE unkey 2 k3 k4 weights 1 0.5 aggregate max
4
127.0.0.1:6380> ZRANGE unkey 0 -1 withscores
yiming
20
sean
60
steven
70
tom
80
127.0.0.1:6380>

4、(面试题)使用sorted_set的时候,排序的原理是什么

使用sorted_set的时候,排序的原理是什么,增删改的速度快不快?是怎样一个速度

这里用到了一个数据结构,Skip list跳跃表

我们都知道链表,有很多元素在这个链表中,如果给他排序的话,即当第二个元素出现的时候,跟第一个元素比较。无非就是放前放后的问题,第三个元素,需要跟第一个比较,如果比第一个元素大,再个第二个比较,随着元素越来越多,比如10000个元素,那么最大遍历10000次,复杂度就变高了

什么是跳跃表

其实是在链表的基础上,又在垂直方向,又多加了几层,牺牲存储空间来换查询速度

指针修正、左右判定

image-20210319155945838

他是类平衡树,即左旋右旋,让所有的深度基本保持一致。使得查找任何元素的复杂度比较均匀

因此,跳跃表的平均值相对最优 。不只是看一个,增删改查都发生一次后,平均速度是最优的。


如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !