List类型
在redis中,List相当于数组或者顺序表。
列表需要注意:
1、列表中的元素是有序的,注意这里的有序并不是升序降序!有序的含义需要根据上下文进行区分的~这里的有序指的是顺序很关键~~如果把元素位置颠倒,顺序调换,此时得到的新的List和之前的List是不等价的!!
同一个词,怎么理解,务必要结合上下文,,结合具体场景~~
如:堆/栈,可以是数据结构的,操作系统,JVM的~
同步,可以是同步和互斥的同步,也可以是同步和异步的同步
2、区分获取和删除的区别
lindex能获取到元素的值,lrem也能返回被删除元素的值。
3、列表中的元素是允许重复的
像hash这样的类型,field是不能重复的。
因为当前List,头和尾都能高效地擦汗如删除元素,就可以把这个List当做一个栈/队列来使用了。Redis可以作为消息队列,最早的时候,就是通过List类型实现的,后来Redis有提供了一个stream类型。
命令
lpush
将一个或者多个元素从左侧头插入list中。
LPUSH key element [element ...]
时间复杂度:插入一个元素O(1),插入多个元素为O(N),N为插入元素个数。
返回值:插入后list的长度。
如果key已经存在,但是key的value类型不是list,此时lpush命令就要报错~(redis中所有的这些各种类型的操作,都是类似的效果)
lrange
通过上述的lpush命令,我们已经能够插入元素了。那么我们要如何查看插入是否成功呢?
使用lrange命令查看list指定范围内的元素。
LRANGE key start stop
时间复杂度:O(N)
返回值:指定区间的元素。
此处描述的区间也是闭区间,下标支持负数。
注意:这里的序号是专门给结果集的序号和list的下标无关!!!
可以看到,hash操作也可能会得到带有序号的结果。此处的序号仅仅是标识下返回元素的顺序,和下标无关,hash类型就没有下标的概念~~
谈到下标,往往会关注超出范围的情况~~Java中,下标超出范围,一般会抛出异常~~因为多出一步下标合法性检验~~
缺点:速度更慢 优点:出现问题及时发现。
而redis的做法:直接尽可能地获取到给定区间地元素,如果给定区间非法,比如超出下表,也会尽可能地获取对应地内容。
rpush
将一个或多个元素尾插入列表中。
RPUSH key element [element ...]
时间复杂度:插入一个元素O(1),插入多个元素为O(N),N为插入元素个数。
返回值:插入后list的长度。

那么有人就会问了,lpush是(l是left的意思)从左侧插入,rpush(r是right的意思)从右侧插入,我们有lrange查询元素,那有没有rrange呢?
没有~这里lrange的l是list的含义,没有rrange。
rpushx
在key存在时,将一个或者多个元素从右侧放入(尾插)到list中。(这里的x是exists,存在的意思)
RPUSHX key element [element ...]
时间复杂度:插入一个元素O(1),插入多个元素为O(N),N为插入元素个数。
返回值:插入后list的长度。
lpop
从list左侧取出元素(头删)。
LPOP key
时间复杂度:O(1)。
返回值:取出的元素或者nil。
rpop
从list的右侧取出元素(尾删)。
RPOP key
时间复杂度:O(1)。
返回值:取出的元素或者nil。
redis中的list是一个双端队列~~
从两头插入/删除元素都是非常高效的O(1),搭配使用rpush和lpop或者lpush和rpop,就相当于队列了;搭配使用lpush和lpop或者rpush和rpop,就相当于栈了。
lindex
给定下标,获取元素。
LINDEX key index
时间复杂度:O(N),此处的N指的是列表中元素的个数。
返回值:取出的元素或者nil。
linsert
在特定位置插入元素。
LINSERT key <BEFORE | AFTER> pivot element
ps:pivot表示基准(列表中的元素),before/after表示在基准之前/后,element表示当前要插入的元素。
时间复杂度:O(N),N是列表的长度。
返回值:插入之后,得到的新list的长度。
那么问题来了,万一基准值存在多个怎么办呢?
llen
获取list的长度。
LLEN key
时间复杂度:O(1)。
返回值:list的长度。
lrem
删除指定数量的元素。
LREM key count element
ps:count:要删除的个数,element:要删除的值。
时间复杂度:O(N+M),N为列表中长度的个数,M为要删除的元素个数,可近似看为O(N)
返回值:删除的元素的个数。
count>0:从前往后删除。
count<0:从后往前删除。
count=0:删除所有的元素。


观察文档,我们会发现这一行东西:
这是啥呢?
这个叫access control list------访问控制列表(权限),是从Redis6以上才开始支持的。Redis有很多命令 ,acl这块就把每个命令都打上一些标签,打好标签之后,管理员就可以给每个redis用户配置不同的权限(运行该用户可以执行哪些标签对应的命令)。
ltrim
保留start和stop之间区间内的元素(区间外面两边的元素就直接被删除了)。
LTRIM key start stop
时间复杂度:O(N)
lset
根据下标,修改元素。
LSET key index element
时间复杂度:O(N)

blpop和brpop
这两个命令是lpop和rpop的阻塞版本,如果list中存在元素,blpop和brpop就和lpop以及rpop的作用完全相同;如果list为空blpop和brpop就会产生阻塞,一直阻塞到队列不空为止。
BLPOP key [key ...] timeout
BRPOP key [key ...] timeout
ps:timeout是超时时间,单位是秒。
这个东西就有点像我们之前讲过的阻塞队列:
使用队列来作为中间的"交易场所",期望这个队列有两个特性:
1、线程安全
2、阻塞
- 如果队列为空,尝试出队列,就会产生阻塞,知道队列不空,阻塞解除。
- 如果队列为满,尝试入队列,也会产生阻塞,知道队列不满,阻塞解除
redis中的list也相当于阻塞队列一样,线程安全是通过单线程模型支持。不同的是redis阻塞只支持"队列为空"的情况,不考虑"队列满"的情况。
命令特点:
1、阻塞版本会根据timeout,阻塞一段时间,期间Redis可以执行其他的命令。因此,此处的blpop和brpop看起来好像耗时很久,但是实际上并不会对redis服务器产生负面影响!!!
2、命令中如果设置了多个键,那么会从左向右进行遍历键,一旦有一个键对应的列表中可以弹出元素,命令立即返回。通俗点说,blpop和brpop都是可以同时去获取多个key的列表的元素的~~多个key对应多个list,这多个list哪个有元素了,就会返回哪个元素。
3、如果多个客户端同时对一个key执行pop,则最先执行命令的客户端会得到弹出的元素。




小结
list的编码方式
以前,list的编码方式有两种:压缩列表和链表。list现在的编码方式只有一种:quicklist,这个quicklist相当于链表和压缩列表的组合。quicklist整体还是一个链表,链表的每个节点,是一个压缩列表。
压缩列表的缺点是当元素个数多了,操作起来效率会降低,因为每次操作元素都涉及到编码的转换。使用上述quicklist的方式,就能使每个压缩列表,都不让它太大,同时再把多个压缩列表通过链式结构连起来~~
进入配置文件,可以看到:

list类型的应用场景
1、用list作为"数组"这样的结构,来存储多个元素。
例如:
结论:使用哈希类型来表示结构化数据,如:学生、班级这样的数据,使用list来表示它们之间的关联关系。
2、使用redis作为消息队列

3、Redis分频道阻塞消息队列模型
4、微博Timeline
每个用户都有属于自己的Timeline(微博列表),现需要分页展示文章列表。此时可以考虑使用列表,同时支持按照索引范围获取元素。
1、每篇微博使用哈希结构存储,例如微博中的三个属性:title、timestamp、content:
hmset mblog:1 title xx timestamp 1476536196 content xxxxx
...
hmset mblog:n title xx timestamp 1476536196 content xxxxx
2、向用户Timeline添加微博,user:<uid>:mblogs作为微博的键:
lpush user:1:mblogs mblog:1 mblog:3
...
lpush user:k:mblogs mblog:9
3、分页获取用户的Timeline,例如获取用户1的前10篇微博:
keylist = lrange user:1:mblogs 0 9
for key in keylist {
hgetall key
}
此方案实际存在两个问题:
1、1+n问题。如果每次分页获取的微博个数较多,需要执行多次hgetall操作,此时可以考虑使用pipeline(流水线)模式(将这多次hgetall命令打包成一次)批量提交命令,或者微博不采用哈希类型,而是使用序列字符串类型,使用mget获取。
2、分裂获取文章时,lrange在列表两端表现较好,获取列表中间的元素表现较差,此时可以考虑将列表做拆分。



