哈喽,上节课我们介绍了redis的最基础的数据类型String,这节课我们自然是要继续学习redis的数据类型,这节课我便开始介绍redis的另一个数据类型:List列表。

List简述

大家学过Python的朋友都知道Python里面有一种数据类型叫做List,当然redis中的ListPython中的List还是不一样的,大家认真学过数据结构的都知道有一种数据结构叫做链表。而redis的列表便是一个双向链表的结构

\rightleftarrows\overline{\underline{\ \ \boxed{\begin{matrix}v_1\end{matrix}}\ \ \rightleftarrows\ \ \boxed{\begin{matrix}v_2\end{matrix}}\ \ \rightleftarrows\ \ \begin{matrix}\dots\end{matrix}\ \ \rightleftarrows\ \ \boxed{\begin{matrix}v_{n-1}\end{matrix}}\ \ \rightleftarrows\ \ \boxed{\begin{matrix}v_n\end{matrix}}\ \ }}\rightleftarrows

就像上面的这个结构,这个链表是双向的,我们可以可以添加元素到链表的头部或者尾部,如果从链表的头部一直找下一个元素可以找到尾部,反之如果从链表的尾部一直找上一个元素也一样能够找到头部。我们也可以将整个链表设计成一个双向循环链表,让尾部元素的下一个元素是头部元素,这样的话整个链表便可以练成一个环状结构。

而这个结构也是有缺点的,因为两端的操作性能极高,通过索引取中间节点的性能较差。

List常用命令

List做了简单的介绍之后,那么我们来看一下List的一些常用的用法。

lpush / rpush <key> <value1> <value2>...

该命令的作用是从列表左侧或者右侧添加元素,而且也可以添加多个元素。我们来看一下:

image-20220425105145858

我们看见每次执行命令之后都会输出一个数字,这个数字是k1对应列表的长度。但是我们怎么来查看这个列表中的值呢?我们先不着急,我们先来看另一个命令

lrange <key> <起始位置> <结束位置>

这个命令就是我们所关心的问题,这个命令通过索引获得列表某一段的值,什么意思呢,就是我可以通过这个命令来获取列表从左往右数起始索引到结束索引之间的值,比如:

image-20220425124308021

我们用lrange命令来获取列表第 1 位到列表最后一位之前的值,我们看见输出了我们之前设置好的值。但是有一个问题就是我们输出的值的顺序仿佛和我们预期的相反。那么我们来看一下为什么:

\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\ \ \Rightarrow\ \ \overset{通过\ lpush\ 添加\ v1}{\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ lpush\ 添加\ v2}{\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ lpush\ 添加\ v3}{\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ lpush\ 添加\ v4}{\boxed{\begin{matrix}v4\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}}

我们来看一下,因为我们是用lpush来添加数据。首先最开始我们没有列表的,但是为了方便理解,我用一个长度为 4 的空列表来代替,然后我们执行了lpush命令,虽然这个命令一次性添加了 4 个元素,但底层依然还是要一个元素一个元素往列表里添加,所以我们就看见在列表中添加第一个元素时,是在列表最左侧添加了第一个元素。

紧接着添加第二个元素时,依然还是在列表最左侧添加,那么第一个元素就会被压入第二位,然后第三个元素与第四个元素以此类推,故而最终列表从左到右便依次是v4、v3、v2、v1

而图中我们执行lrange命令是从左到右依序取值。所以说最终输出的元素才会是倒序的。

\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\ \ \Rightarrow\ \ \overset{通过\ rpush\ 添加\ v1}{\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ rpush\ 添加\ v2}{\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ rpush\ 添加\ v3}{\boxed{\begin{matrix}\ \ \ \ \end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}}\ \ \Rightarrow\ \ \overset{通过\ rpush\ 添加\ v4}{\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}}

当然rpush命令的原理也一样,只是rpush命令是从列表最右侧开始添加元素,这里就不再赘述。

lindex <key> <index>

我们上面介绍了如何根据索引来获取列表某一段中的元素,那么我们能不能直接通过索引来取到列表中某一个元素呢?那么lindex命令便可以满足需求,这个命令就可以根据索引从列表获取元素: image-20220425135521201

我们从图中可以看见,首先我们新增了一个列表,从左往右v4、v3、v2、v1,然后通过lindex命令,获取索引为 1 的元素,也就是从左往右数第二个元素,然后输出的便是v3

lpop / rpop <key>

这个命令可以从列表的左侧或者右侧弹出一个元素,当列表中所有元素被弹出,则key也会失效,那么什么叫做弹出一个元素呢?我们来看一下:

k1\ \ \to\ \ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\boxed{\begin{matrix}v5\end{matrix}}\boxed{\begin{matrix}v6\end{matrix}}\ \ \stackrel{lpop\ k1}{==\Longrightarrow}\ \ \boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\boxed{\begin{matrix}v5\end{matrix}}\boxed{\begin{matrix}v6\end{matrix}}

我们来看上面这个过程,我们执行lpop命令会从列表左侧弹出一个元素。与此同时,列表中的元素也自然就消失了一个。而且还有一个特点就是,当列表中所有元素都被弹出了之后,这个列表的key也就失效了:

image-20220425113935364

同样的rpop命令也是一样,只不过是从列表右侧弹出元素,这里也就不再赘述了。

ropolpush <key1> <key2>

这个命令就有意思了,这个命令可以从key1的列表中弹出一个元素并添加到key2列表的左侧,我们来看一下效果:

image-20220425132915037

首先我们设置k1k2,然后然后执行rpoplpush命令之后我们看一下k2对应的列表。就是把k1列表中最右侧的值弹出添加到k2列表的最左侧。

\left.\begin{matrix} key1\ \to\ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\\\\ key2\ \to\ \boxed{\begin{matrix}v11\end{matrix}}\boxed{\begin{matrix}v12\end{matrix}}\boxed{\begin{matrix}v13\end{matrix}}\boxed{\begin{matrix}v14\end{matrix}}\ \end{matrix}\right\}\ \stackrel{执行rpoplpush命令}{========\Longrightarrow}\ \begin{cases} key1 \to\ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\\\\ key2\ \to\ \boxed{\begin{matrix}v4\end{matrix}}\boxed{\begin{matrix}v11\end{matrix}}\boxed{\begin{matrix}v12\end{matrix}}\boxed{\begin{matrix}v13\end{matrix}}\boxed{\begin{matrix}v14\end{matrix}} \end{cases}

这个命令的底层所执行的操作正如上面所展示的一样,最终的结果同时更改了两个列表。

llen <key>

这个命令就很简单了,就是用于获取指定key对应列表的长度,不再赘述了。

linsert <key> before / after <value> <new_value>

这个命令还是比较有意思的,我们之前说过,我们可以将一个值添加到一个列表的头部或者尾部,那么我们如果想在列表中间节点添加一个元素该怎么办呢?这个命令的用途就是在列表某个值之前或者之后插入一个新的值。

k1\ \to \ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\ \ \stackrel{通过\ linsert\ k1\ beofre\ v3\ v21\ 命令插入值}{===============\Longrightarrow}\ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v21\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}

这个命令底层是怎么做的呢?我们来看一下,linsert k1 beofre v3 v21命令,解释一下,我们要在k1列表的v3的值之前插入一个新的值v21,这里该命令不需要借助索引。我们来看一下效果:

image-20220425141404052

我们看见执行了linsert命令之后,输出的便是修改后的列表的长度,然后我们用lrange查看一下在v3之前已经插入了我们指定的值。

lrem <key> <n> <value>

这个命令也是很有用的,这个命令可以从列表左侧开始删除掉n个值为指定value的元素:

k1\ \to \ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\ \ \stackrel{通过\ lrem\ k1\ 3\ v1\ 命令删除值}{==========\Longrightarrow}\ \ \boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}

我们来看一下底层的操作,首先我们有一个k1列表:v1、v2、v1、v3、v1、v4,我们看见了列表中有三个v1,那么我们可以用lrem k1 3 v1命令来删除这三个v1。那么我们来看一下实际操作的效果:

image-20220425142803545

这里的 3 是指要删除的个数,如果将这个数字改成 2,那么就只能删除前两个v1

lset <key> <index> <value>

这个命令就很简单了,这个命令是为了将列表指定下标的元素设置为指定value

k1\ \to \ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v2\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}\ \ \stackrel{通过\ lset\ k1\ 1\ v11\ 命令修改值}{==========\Longrightarrow}\ \ \boxed{\begin{matrix}v1\end{matrix}}\boxed{\begin{matrix}v11\end{matrix}}\boxed{\begin{matrix}v3\end{matrix}}\boxed{\begin{matrix}v4\end{matrix}}

我们只要指定好列表的key、索引和value即可,然后我们来看一下实际效果:

image-20220425143932116

我们成功将索引为 1 的元素改成了我们指定的value

底层数据结构

我们介绍了List的基本用法,那么我们来介绍一下底层的数据结构吧。

简单来说redis的列表底层数据结构为快速链表quickList。在元素比较少的情况下会使用一块连续内存进行存储,这个结构是压缩列表也就是zipList。随着元素数量的增加,将会在内存中开辟多个压缩列表,最终将这些压缩列表组成一个快速链表。

\boxed{zipList}\ \leftrightarrow\ \boxed{zipList}\ \leftrightarrow\ \dots\ \leftrightarrow\ \boxed{zipList}\ \leftrightarrow\ \boxed{zipList}

因为普通链表需要附加指针空间,而redis是将链表和zipList结合起来组成quickList其实就是用双向指针将zipList串起来使用这样既不会造成太大的空间冗余也能满足快速插入删除的性能

总结

  • redis列表是一个双向链表

  • 常用命令:

    • lpush / rpush <key> <value1> <value2>...
    • lrange <key> <起始位置> <结束位置>
    • lindex <key> <index>
    • lpop / rpop <key>
    • ropolpush <key1> <key2>
    • llen <key>
    • linsert <key> before / after <value> <new_value>
    • lrem <key> <n> <value>
    • lset <key> <index> <value>

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/redis-datatype-list/