如果要对多个数据进行按顺序排列,那么前面讲到的string和hash都无法办到。这时候我们就需要引入一种新的数据类型,list。
文章目录
- list数据类型
- 三种数据结构
- list的操作
- 注意事项
- 实际案例
list数据类型
- 数据存储需求
为了解决存储多个数据,并对数据按照顺序进行区分的需求
- 底层结构
list数据在底层是采用双向链表(Double Linked List)存储结构来实现。所以操作分为左操作和右操作。
这里补充下三种数据结构的知识
三种数据结构
- 顺序表
数据在内存中相邻,优点是查询非常快,但是插入删除操作慢
- 单向链表
数据在内存中不相邻,每个数据有自己的内容和下一个数据的指针。插入删除非常快,但是遍历非常慢。
- 双向链表
在单向链表的基础上,每个数据还有前一个数据的指针。从任意一个结点开始,可以查找链表中的其他任意结点。既可以依照后继的方向(向后)遍历,也可以依照前驱的方向(向前)遍历。每个指针域中都增加了一个存储指针的空间,降低了存储密度。可以在当前结点前面或者后面插入,可以删除前趋和后继(包括结点自己),单链表只能在结点后面插入和删除。双向链表通过增加一定的空间复杂度,降低了向前遍历的时间复杂度。
list的操作
- lpush key value1 [value2 …]
从左侧添加数据
127.0.0.1:6380> lpush list1 kobe james harden
(integer) 3
127.0.0.1:6380> lpush list1 yaoming
(integer) 4
127.0.0.1:6380>
- lrange key start stop
从左侧获取数据,start和stop都是索引,从0开始。最后添加的显示在最上面
127.0.0.1:6380> lrange list1 0 3
1) "yaoming"
2) "harden"
3) "james"
4) "kobe"
127.0.0.1:6380>
和python一样,可以用-1表示最后一个索引
127.0.0.1:6380> lrange list1 0 -1
1) "xiaofu"
2) "yaoming"
3) "harden"
4) "james"
5) "kobe"
127.0.0.1:6380>
- rpush key value1 [value2 …]
从右边添加值,再查看的话从右添加的在最下面,越后添加的越靠下
127.0.0.1:6380> rpush list1 davis
(integer) 6
127.0.0.1:6380> rpush list1 chris
(integer) 7
127.0.0.1:6380> lrange list1 0 -1
1) "xiaofu"
2) "yaoming"
3) "harden"
4) "james"
5) "kobe"
6) "davis"
7) "chris"
127.0.0.1:6380>
- lindex key index
返回某个索引处的值
7.0.0.1:6380> lrange list1 0 -1
1) "xiaofu"
2) "yaoming"
3) "harden"
4) "james"
5) "kobe"
6) "davis"
7) "chris"
127.0.0.1:6380> lindex list1 1
"yaoming"
127.0.0.1:6380> lindex list1 2
"harden"
127.0.0.1:6380>
- llen key
返回list的长度
127.0.0.1:6380> llen list1
(integer) 7
127.0.0.1:6380>
- lpop/rpop key
从左边或者右边弹出一个值,list中会将这个值删除
7.0.0.1:6380> lpop list1
"xiaofu"
127.0.0.1:6380> rpop list1
"chris"
127.0.0.1:6380> lrange list1 0 -1
1) "yaoming"
2) "harden"
3) "james"
4) "kobe"
5) "davis"
127.0.0.1:6380>
- blpop/brpop key1 [key2 …] timeout
这里的b代表block,阻塞的意思。也就是说如果list里面暂时没有内容可以pop,就会一直等待,直到timeout时间到为止。这里采用A和B两个客户端来进行演示
A客户端:
127.0.0.1:6380> lpush list2 a b
(integer) 2
127.0.0.1:6380> lpop list2
"b"
127.0.0.1:6380> lpop list2
"a"
127.0.0.1:6380> lpop list2
(nil)
127.0.0.1:6380>
可以发现目前list2已经空了,不能再进行lpop了,此时改为blpop
127.0.0.1:6380> blpop list2 15
执行完以后A客户端进入了阻塞状态,然后去B客户端进行操作
B客户端:
127.0.0.1:6380> rpush list2 d
(integer) 1
127.0.0.1:6380>
这里采用lpush也是可以的
再去A客户端看
A客户端:
127.0.0.1:6380> blpop list2 15
1) "list2"
2) "d"
(10.12s)
127.0.0.1:6380>
从list2获取到一个值d,一共耗时10.12秒
需要注意的是这个命令后面可以接多个key,任意一个key可以pop出来就解除阻塞
A客户端:
127.0.0.1:6380> lpush list3 1
(integer) 1
127.0.0.1:6380> lpop list3
"1"
127.0.0.1:6380> lpop list3
(nil)
127.0.0.1:6380> blpop list2 list3 15
B客户端:
127.0.0.1:6380> rpush list3 2
(integer) 1
127.0.0.1:6380>
A客户端:
127.0.0.1:6380> blpop list2 list3 15
1) "list3"
2) "2"
(6.67s)
127.0.0.1:6380>
从list3获得一个2,耗时6.67秒
- lrem key count value
从一个list中从左往右删除count个值为value的节点。
比较典型的就是朋友圈点赞场景的取消点赞功能,从左到右依次rpush点赞人的名字,此时如果有人想取消点赞,相当于从中间要删除。
或者是嘀嘀打车排队场景,如果有人退出不排了也相当于从中间删除
127.0.0.1:6380> rpush didi a b c d e
(integer) 5
127.0.0.1:6380> lrange didi 0 -1
1) "a"
2) "b"
3) "c"
4) "d"
5) "e"
127.0.0.1:6380> lrem didi 1 c
(integer) 1
127.0.0.1:6380> lrange didi 0 -1
1) "a"
2) "b"
3) "d"
4) "e"
127.0.0.1:6380>
还有一些场景有重复的元素也可以进行删除
127.0.0.1:6380> lpush didi a c e a r
(integer) 9
127.0.0.1:6380> lrange didi 0 -1
1) "r"
2) "a"
3) "e"
4) "c"
5) "a"
6) "a"
7) "b"
8) "d"
9) "e"
127.0.0.1:6380> lrem didi 2 a
(integer) 2
127.0.0.1:6380> lrange didi 0 -1
1) "r"
2) "e"
3) "c"
4) "a"
5) "b"
6) "d"
7) "e"
127.0.0.1:6380>
这里是从左往右进行删除,所以最右边的没有被删除
注意事项
- list保存的数据都是string类型的
- list具有索引的概念,但是操作数据通常以队列(FIFO/先进先出)或者栈(LIFO/后进先出)的形式
- 获取最后索引为-1
- list可以对数据进行分页操作,通常第一页的数据来自于list,第二页及更多的信息通过数据库的形式加载
实际案例
运维人员管理4台机器,每台机器都按时间顺序产生log,这时候如何让运维人员能更方便的查看所有的log?
思路:创建一个redis实例,将四台机器的log都rpush到一个list中,这样旧的log在最左边,新的日志在最右边。如果要读取log,就从左开始读起。而且因为redis是单进程,自带原子性,所以不用担心同时操作的问题。
A机器
127.0.0.1:6380> rpush logs aaa1
(integer) 1
127.0.0.1:6380> rpush logs aaa2
(integer) 2
127.0.0.1:6380> rpush logs aaa3
(integer) 7
127.0.0.1:6380>
B机器
127.0.0.1:6380> rpush logs bbb1
(integer) 3
127.0.0.1:6380> rpush logs bbb2
(integer) 6
127.0.0.1:6380> rpush logs bbb3
(integer) 9
127.0.0.1:6380>
C机器
127.0.0.1:6380> rpush logs ccc1
(integer) 4
127.0.0.1:6380> rpush logs ccc2
(integer) 5
127.0.0.1:6380> rpush logs ccc3
(integer) 8
127.0.0.1:6380>
上面的三台机器虽然看起来内容差不多,但是其实操作的时间是互相交错的。这时候如果有一台机器去查看log
127.0.0.1:6380> lrange logs 0 -1
1) "aaa1"
2) "aaa2"
3) "bbb1"
4) "ccc1"
5) "ccc2"
6) "bbb2"
7) "aaa3"
8) "ccc3"
9) "bbb3"
127.0.0.1:6380>
如果要查看最新的3条log可以
127.0.0.1:6380> lrange logs -3 -1
1) "aaa3"
2) "ccc3"
3) "bbb3"
127.0.0.1:6380>
这样子就利用redis的list数据类型做成了一个简单的日志收集工具和查询工具