如果要对多个数据进行按顺序排列,那么前面讲到的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数据类型做成了一个简单的日志收集工具和查询工具