Redis链表结构深入

链表结构是 Redis 中一个常用的结构,它可以存储多个字符串,而且它是有序的,能够存储 2 的 32 次方减 1 个节点(超过 40 亿个节点)。

Redis 链表是双向的,因此即可以从左到右,也可以从右到左遍历它存储的节点,链表结构如下图所示。
在这里插入图片描述

由于是双向链表,所以只能够从左到右,或者从右到左地访问和操作链表里面的数据节点。但是使用链表结构就意味着读性能的丧失,所以要在大量数据中找到一个节点的操作性能是不佳的,因为链表只能从一个方向中去遍历所要节点。

比如从查找节点 10 000 开始查询,它需要按照节点 1、节点 2、节点 3……直至节点 10 000,这样的顺序查找,然后把一个个节点和你给出的值比对,才能确定节点所在。如果这个链表很大,如有上百万个节点,可能需要遍历几十万次才能找到所需要的节点,显然查找性能是不佳的。

而链表结构的优势在于插入和删除的便利,因为链表的数据节点是分配在不同的内存区域的,并不连续,只是根据上一个节点保存下一个节点的顺序来索引而已,无需移动元素。其新增和删除的操作如下图所示。
在这里插入图片描述

上述的的阿拉伯数字代表新增的步骤,而汉字数字代表删除步骤。

1 新增节点
对插入图中的节点 4 而言,先看从左到右的指向,先让节点 4 指向节点 1 原来的下一个节点,也就是节点 2,然后让节点 1 指向节点 4,这样就完成了从右到左的指向修改。再看从右到左,先让节点 4 指向节点 1,然后节点 2 指向节点 4,这个时候就完成了从右到左的指向,那么节点 1 和节点 2 之间的原有关联关系都已经失效,这样就完成了在链表中新增节点4的功能。

2 删除节点
对删除图中的节点 3 而言,首先让节点 2 从左到右指向后续节点,然后让后续节点指向节点 2,这样节点 3 就脱离了链表,也就是断绝了与节点 2 和后继节点的关联关系,然后对节点 3 进行内存回收,无须移动任何节点,就完成了删除。

由此可见,链表结构的使用是需要注意场景的,对于那些经常需要对数据进行插入和删除的列表数据使用它是十分方便的,因为它可以在不移动其他节点的情况下完成插入和删除。而对于需要经常查找的,使用它性能并不佳,它只能从左到右或者从右到左的查找和比对。

因为是双向链表结构,所以 Redis 链表命令分为左操作和右操作两种命令,左操作就意味着是从左到右,右操作就意味着是从右到左。

Redis关于链表的命令
在这里插入图片描述

上表所示的链表命令,其中以“l”开头的代表左操作,以“r”开头的代表右操作。对于很多个节点同时操作的,需要考虑其花费的时间,链表数据结构对于查找而言并不适合于大数据,而 Redis 也给了比较灵活的命令对其进行操作。

Redis关于链表的操作命令
在这里插入图片描述

这里展示了关于 Redis 链表的常用命令,只是对于大量数据操作的时候,我们需要考虑插入和删除内容的大小,因为这将是十分消耗性能的命令,会导致 Redis 服务器的卡顿。对于不允许卡顿的一些服务器,可以进行分批次操作,以避免出现卡顿。

需要指出的是,之前这些操作链表的命令都是进程不安全的,因为当我们操作这些命令的时候,其他 Redis 的客户端也可能操作同一个链表,这样就会造成并发数据安全和一致性的问题,尤其是当你操作一个数据量不小的链表结构时,常常会遇到这样的问题。

为了克服这些问题,Redis 提供了链表的阻塞命令,它们在运行的时候,会给链表加锁,以保证操作链表的命令安全性,如下表所示。

链表的阻塞命令

在这里插入图片描述
当使用这些命令时,Redis 就会对对应的链表加锁,加锁的结果就是其他的进程不能再读取或者写入该链表,只能等待命令结束。加锁的好处可以保证在多线程并发环境中数据的一致性,保证一些重要数据的一致性,比如账户的金额、商品的数量。

不过在保证这些的同时也要付出其他线程等待、线程环境切换等代价,这将使得系统的并发能力下降。

Redis 链表阻塞操作命令
在这里插入图片描述

在项目中,虽然阻塞可以有效保证了数据的一致性,但是阻塞就意味着其他进程的等待,CPU 需要给其他线程挂起、恢复等操作,更多的时候我们希望的并不是阻塞的处理请求,所以这些命令在实际中使用得并不多。

使用 Spring 去操作 Redis 链表的命令,这里继续保持代码关于 RedisTemplate 的配置,在此基础上获取 RedisTemplate 对象,然后输入以下代码。

public static void testList() {ApplicationContext applicationcontext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate = applicationcontext.getBean(RedisTemplate.class);try {//删除链表,以便我们可以反复测试redisTemplate.delete("list");//把node3插入链表listredisTemplate. opsForList ().leftPush ("list", "node3");List<String> nodeList = new ArrayList<String>();for (int i = 2; i >= 1; i--){nodeList.add("nnode" + i);}//相当于lpush把多个价值从左插入链表redisTemplate.opsForList().leftPushAll("list", nodeList);//从右边插入一个节点redisTemplate.opsForList().rightPush("list", "node4");//获取下标为0的节点String nodel = (String) redisTemplate.opsForList() .index("list", 0);//获取链表长度long size = redisTemplate.opsForList ().size ("listn");//从左边弹出一个节点String lpop = (String) redisTemplate.opsForList().leftPop("list");//从右边弹出一个节点String rpop = (String) redisTemplate.opsForList().rightPop("list");//注意,需要使用更为底层的命令才能操作linsert命令//使用linsert命令在node2前插入一个节点redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),RedisListCommands.Position.BEFORE,"node2".getBytes("utf-8"),"before_node".getBytes("utf-8"));//使用linsert命令在node2后插入一个节点redisTemplate.getConnectionFactory().getConnection().linsert("list".getBytes("utf-8"),RedisListCommands.Position.AFTER,"node2".getBytes("utf-8"), "after_node".getBytes("utf-8"));//判断list是否存在,如果存在则从左边插入head节点redisTemplate.opsForList().leftPushlfPresent("list", "head");//判断list是否存在,如果存在则从右边插入end节点redisTemplate.opsForList().rightPushlfPresent("list", "end");//从左到右,或者下标从0到10的节点元素List valueList = redisTemplate.opsForList().range("list", 0, 10);nodeList.clear();for (int i = 1; i <= 3; i++) {nodeList.add("node");}//在链表左边插入三个值为node的节点redisTemplate.opsForList().leftPushAll.("list", nodeList);//从左到右删除至多三个node节点redisTemplate.opsForList().remove("list", 3,"node");//给链表下标为0的节点设置新值redisTemplate.opsForList().set("list",0, "new_head_value");} catch (UnsupportedEncodingException ex) {ex.printStackTrace();}//打印链表数据printList(redisTemplate, "list");
}
public static void printList(RedisTemplate redisTemplate, String key) { 
//链表长度Long size = redisTemplate.opsForList().size(key);
}

这里所展示的是 RedisTemplate 对于 Redis 链表的操作,其中 left 代表左操作,right 代表右操作。有些命令 Spring 所提供的 RedisTemplate 并不能支持,比如 linsert 命令,这个时候可以使用更为底层的方法去操作,正如代码中的这段:

// 使用linsert命令在node2前插入一个节点
redisTemplate.getConnectionFactory().getConnection().lInsert("list".getBytes("utf-8"),
RedisListCommands.Position.BEFORE,"node2".getBytes("utf-8"),
"before_node".getBytes("utf-8"));

在多值操作的时候,往往会使用 list 进行封装,比如 leftPushAll 方法,对于很大的 list 的操作需要注意性能,比如 remove 这样的操作,在大的链表中会消耗 Redis 系统很多的性能。

Redis 还有对链表进行阻塞操作的命令,这里 Spring 也给出了支持,代码如下所示。

public static void testBList()    {ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");RedisTemplate redisTemplate    = applicationContext.getBean(RedisTemplate.class);// 清空数据,可以重复测试redisTemplate.delete ("list1");redisTemplate.delete ("list2");//初始化链表 list1List<String> nodeList = new ArrayList<String>();for (int i=1; i<=5; i++)    {nodeList.add("node" + i);}redisTemplate.opsForList().leftPushAll("list1", nodeList);// Spring 使用参数超时时间作为阻塞命令区分,等价于 blpop 命令,并且可以设置时间参数 redisTemplate.opsForList().leftPop ("list1", 1, TimeUnit.SECONDS);// Spring 使用参数超时时间作为阻塞命令区分,等价于 brpop 命令,并且可以设置时间参数redisTemplate.opsForList().rightPop("list1", 1, TimeUnit.SECONDS);nodeList.clear();// 初始化链表 list2for (int i=1; i<=3; i++)    {nodeList.add("dato" + i);}redisTemplate.opsForList().leftPushAll("list2", nodeList);// 相当于 rpoplpush 命令,弹出 list1 最右边的节点,插入到 list2 最左边redisTemplate.opsForList().rightPopAndLeftPush("list1","list2");// 相当于brpoplpush命令,注意在 Spring 中使用超时参数区分 redisTemplate.opsForList().rightPopAndLeftPush("list1", "list2",1,TimeUnit.SECONDS);// 打印链表数据printList(redisTemplate, "list1");printList(redisTemplate, "list2");
}

这里展示了 Redis 关于链表的阻塞命令,在 Spring 中它和非阻塞命令的方法是一致的,只是它会通过超时参数进行区分,而且我们还可以通过方法设置时间的单位,使用还是相当简单的。注意,它是阻塞的命令,在多线程的环境中,它能在一定程度上保证数据的一致而性能却不佳。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/564097.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

家用使用计算机组装,不能再简单了!家用电脑DIY组装实操

【天极网DIY硬件频道】【天极网硬件频道】近期有网友对DIY组装电脑比较感兴趣&#xff0c;因为自从智能手机和平板电脑横行霸道之后&#xff0c;家中的PC电脑被使用的时间变少了许多&#xff0c;可是偶尔有工作需求或是别有任务等&#xff0c;所以大家依然是不会让电脑远离的。…

Redis集合深入

Redis 的集合不是一个线性结构&#xff0c;而是一个哈希表结构&#xff0c;它的内部会根据 hash 分子来存储和查找数据&#xff0c;理论上一个集合可以存储 2 的 32 次方减 1 个节点&#xff08;大约 42 亿&#xff09;个元素&#xff0c;因为采用哈希表结构&#xff0c;所以对…

Redis有序集合详解

有序集合和集合类似&#xff0c;只是说它是有序的&#xff0c;和无序集合的主要区别在于每一个元素除了值之外&#xff0c;它还会多一个分数。分数是一个浮点数&#xff0c;在 Java 中是使用双精度表示的&#xff0c;根据分数&#xff0c;Redis 就可以支持对分数从小到大或者从…

如何为你的网站启用HTTPS

步骤一&#xff1a;获取SSL/TLS证书 选择SSL证书提供商&#xff1a; 选择一家可信赖的SSL证书提供商。对于小型网站&#xff0c;JoySSL提供的免费证书是一个不错的选择。购买或申请证书&#xff1a; 根据你的网站需求&#xff0c;购买相应类型的SSL证书。证书的类型包括单域、…

Redis HyperLogLog常用命令

基数并不是存储元素&#xff0c;存储元素消耗内存空间比较大&#xff0c;而是给某一个有重复元素的数据集合&#xff08;一般是很大的数据集合&#xff09;评估需要的空间单元数&#xff0c;所以它没有办法进行存储&#xff0c;加上在工作中用得不多&#xff0c;我们要介绍一下…

Redis的基础事务

Redis 存在事务&#xff0c;尽管它没有数据库那么强大&#xff0c;但是它还是很有用的&#xff0c;尤其是在那些需要高并发的网站当中。 使用 Redis 读/写数据要比数据库快得多&#xff0c;如果使用 Redis 事务在某种场合下去替代数据库事务&#xff0c;则可以在保证数据一致性…

Redis事务回滚深入

对于 Redis 而言&#xff0c;不单单需要注意其事务处理的过程&#xff0c;其回滚的能力也和数据库不太一样&#xff0c;这也是需要特别注意的一个问题——Redis 事务遇到的命令格式正确而数据类型不符合&#xff0c;如图所示。 从图中可知&#xff0c;我们将 key1 设置为字符串…

计算机辅助设计的发展及应用,计算机辅助设计的发展与应用

计算机维普资讯 http://doc.xuehai.net第 1卷第 34 2、期19 98年 9月株洲工学院学报V 11 o34 o 2N .、 .J OURNAL OF HUZ Z HOU NS TUTE OF TEC I TI HNOL OGY S p 1 9 e .98⑦计算机辅助设计的发展与应用7摘要廖建勇(洲工学院株株洲 420) 1 0 87讨论计算机辅助设计( AD) c技术…

Java 画圆

Java控制台画圆 画空心圆 完整代码&#xff1a; //空心圆 import java.util.Scanner; class circle2 { public static void main(String[] args) { new circle2().print();} //画圆函数void print() {System.out.println("输入半径&#xff1a;"); Sca…

智能控制和计算机控制的区别,解析智能控制技术是什么及与传统控制的区别

描述智能控制是什么智能控制(intelligent controls)在无人干预的情况下能自主地驱动智能机器实现控制目标的自动控制技术。对许多复杂的系统&#xff0c;难以建立有效的数学模型和用常规的控制理论去进行定量计算和分析&#xff0c;而必须采用定量方法与定性方法相结合的控制方…

C++ 画直线

使用C画直线 完整代码 #include "graphics.h" int main() {int driver,mode,i;float x0,y0,y1,x1;float j12,k;driverVGA;modeVGAHI;initgraph(&driver,&mode,"");setbkcolor(GREEN);x0263;y0263;y1275;x1275;for(i0;i<18;i){setcolor(5);lin…

计算机系统性错误,《深入理解计算机系统-异常》

现代操作系统通过使控制流发生突变来对某些意外情况(磁盘读写数据准备就绪、硬件定时器产生信号等)做出反应。一般而言&#xff0c;我们把这些突变命名为异常控制流(Exceptional Contral Flow ECF)。异常控制流发生在计算机系统的各个层次。比如&#xff0c;在硬件层&#xff0…

C 画矩形

使用C画矩形 使用C语言的相关知识在控制台输出一个矩形。 完整代码 #include "graphics.h" int main() {int x0,y0,y1,x1,driver,mode,i;driverVGA;modeVGAHI;initgraph(&driver,&mode,"");setbkcolor(YELLOW);x0263;y0263;y1275;x1275;for(i0;…

C++ 画圆

C画圆 使用C技术在控制台输出一个圆形。 完整代码 # define PAI 3.1415926 # define B 0.809 # include "graphics.h" #include "math.h" int main() {int i,j,k,x0,y0,x,y,driver,mode;float a;driverCGA;modeCGAC0;initgraph(&driver,&mode,&…

C 画图

使用C语言画图 使用C语言相关知识画图。 完整代码 #include "graphics.h" #define LEFT 0 #define TOP 0 #define RIGHT 639 #define BOTTOM 479 #define LINES 400 #define MAXCOLOR 15 int main() {int driver,mode,error;int x1,y1;int x2,y2;int dx1,dy1,dx2,…

C 杨辉三角形

使用C语言实现杨辉三角形 C语言使用循坏输出10行的杨辉三角形。 完整代码&#xff1a; #include <stdio.h>int main() {int i,j;int a[10][10];printf("\n");for(i0;i<10;i) {a[i][0]1;a[i][i]1;}for(i2;i<10;i)for(j1;j<i;j)a[i][j]a[i-1][j-1]a[…

计算机项目开发流程,产品开发项目建议流程图怎样画

产品开发项目建议流程图怎样画2019-01-14 10:30:270点赞3收藏0评论每一个新产品即将问世的时候会对其进行一系列的测试&#xff0c;测试的结果以及需要更改的地方我们会用表格将其整理起来&#xff0c;流程图就是一个比较实用的图表&#xff0c;可以清晰的看到整件事情的操作流…

C 画点

C语言画点 使用C语言画点。 完整代码 #include "stdio.h" #include "graphics.h" int main() {int i,j,driverVGA,modeVGAHI;initgraph(&driver,&mode,"");setbkcolor(YELLOW);for(i50;i<230;i20)for(j50;j<230;j)putpixel(i,j…

C 画椭圆

C语言画圆 使用C语言画椭圆。 完整代码 #include "stdio.h" #include "graphics.h" #include "conio.h" int main() {int x360,y160,driverVGA,modeVGAHI;int num20,i;int top,bottom;initgraph(&driver,&mode,"");topy-30…

C++ 画图

使用C画图 C实现画图&#xff0c;采用ellipse and rectangle实现。 完整代码 #include "stdio.h" #include "graphics.h" #include "conio.h" main() {int driverVGA,modeVGAHI;int i,num15,top50;int left20,right50;initgraph(&driver,…