【2023】Redis实现消息队列的方式汇总以及代码实现

Redis实现消息队列的方式汇总以及代码实现

  • 前言
  • 开始前准备
      • 1、添加依赖
      • 2、添加配置的Bean
  • 具体实现
    • 一、从最简单的开始:List 队列
        • 代码实现
    • 二、发布订阅模式:Pub/Sub
      • 1、使用RedisMessageListenerContainer实现订阅
      • 2、还可以使用redisTemplate实现订阅
    • 三、、 趋于成熟的队列:Stream
      • 具体java代码实现:
  • 总结

前言

经常听到很多人讨论,关于「把 Redis 当作队列来用是否合适」的问题。

有些人表示赞成,他们认为 Redis 很轻量,用作队列很方便。也些人则反对,认为 Redis 会「丢」数据,最好还是用「专业」的队列中间件更稳妥。

这篇文章就聊一聊把 Redis 当作队列,究竟是否合适这个问题。我们会从简单到复杂,一步步带你梳理其中的细节,把常用的实现方式展现一遍。

开始前准备

1、添加依赖

        <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.4.0</version></dependency>

2、添加配置的Bean

避免不方便用软件查看存储的数据

    /*** redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类* @param redisConnectionFactory* @return*/@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);// 使用Jackson2JsonRedisSerialize 替换默认序列化Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);ObjectMapper objectMapper = new ObjectMapper();objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);// 设置value的序列化规则和 key的序列化规则redisTemplate.setKeySerializer(new StringRedisSerializer());//jackson2JsonRedisSerializer就是JSON序列号规则,redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);redisTemplate.afterPropertiesSet();return redisTemplate;}

具体实现

一、从最简单的开始:List 队列

首先,我们先从最简单的场景开始讲起。

如果你的业务需求足够简单,想把 Redis 当作队列来使用,肯定最先想到的就是使用 List 这个数据类型。

因为 List 底层的实现就是一个「链表」,在头部和尾部操作元素,时间复杂度都是 O(1),这意味着它非常符合消息队列的模型。

如果把 List 当作队列,你可以这么来用。

代码实现

生产者端读取:

@RestController
@RequestMapping("/redis01")
public class RedisTest1 {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;//LPUSH 发布消息@GetMapping("/set")public void set(String code){redisTemplate.opsForList().leftPush("code",code);}// RPOP 拉取消息@GetMapping("/get1")public String get1(String key){Object code =  redisTemplate.opsForList().rightPop(key);if (code!=null){return code.toString();}return "redis中没数据!";}

实现模型:
这个模型也非常简单容易理解。 在这里插入图片描述

但这里有个小问题,当队列中已经没有消息了,消费者在执行 RPOP 时,会返回 NULL。
在这里插入图片描述
一般在编写消费者时,会采用一个死循环,这个实现方式就是不断去队列中拉取数据。

    @GetMapping("/get2")public String get2(String key) throws InterruptedException {while (true){Object code = redisTemplate.opsForList().rightPop(key);System.out.println(code);//            读取到消息,退出,没读到继续循环if (code!=null){return code.toString();}}}

如果此时队列为空,那消费者依旧会频繁拉取消息,这会造成「CPU 空转」,不仅浪费 CPU 资源,还会对 Redis 造成压力。

怎么解决这个问题呢?

也很简单,当队列为空时,我们可以「休眠」一会,再去尝试拉取消息。代码可以修改成这样:

    @GetMapping("/get2")public String get2(String key) throws InterruptedException {while (true){Object code = redisTemplate.opsForList().rightPop(key);System.out.println(code);//            读取到消息,退出,没读到继续循环if (code!=null){return code.toString();}Thread.sleep(2000);}}

这就解决了 CPU 空转问题。

这个问题虽然解决了,但又带来另外一个问题:当消费者在休眠等待时,有新消息来了,那消费者处理新消息就会存在「延迟」。

假设设置的休眠时间是 2s,那新消息最多存在 2s 的延迟。

要想缩短这个延迟,只能减小休眠的时间。但休眠时间越小,又有可能引发 CPU 空转问题。

鱼和熊掌不可兼得。

那如何做,既能及时处理新消息,还能避免 CPU 空转呢?

Redis 是否存在这样一种机制:如果队列为空,消费者在拉取消息时就「阻塞等待」,一旦有新消息过来,就通知我的消费者立即处理新消息呢?

幸运的是,Redis 确实提供了「阻塞式」拉取消息的命令:BRPOP / BLPOP,这里的 B 指的是阻塞(Block)。
在这里插入图片描述
在java中也已经封装好了,调用pop方法时,直接设置一个过期时间就行

    @GetMapping("/get3")public String get3(String key) throws InterruptedException {Object code = redisTemplate.opsForList().rightPop(key,0, TimeUnit.SECONDS);if (code==null){return "数据读取超时!";}return code.toString();}

使用 BRPOP 这种阻塞式方式拉取消息时,还支持传入一个「超时时间」,如果设置为 0,则表示不设置超时,直到有新消息才返回,否则会在指定的超时时间后返回 NULL。

这个方案不错,既兼顾了效率,还避免了 CPU 空转问题,一举两得。

注意:如果设置的超时时间太长,这个连接太久没有活跃过,可能会被 Redis Server 判定为无效连接,之后 Redis Server
会强制把这个客户端踢下线。所以,采用这种方案,客户端要有重连机制。

解决了消息处理不及时的问题,你可以再思考一下,这种队列模型,有什么缺点?

我们一起来分析一下:

  1. 不支持重复消费:消费者拉取消息后,这条消息就从 List 中删除了,无法被其它消费者再次消费,即不支持多个消费者消费同一批数据
  2. 消息丢失:消费者拉取到消息后,如果发生异常宕机,那这条消息就丢失了
    第一个问题是功能上的,使用 List 做消息队列,它仅仅支持最简单的,一组生产者对应一组消费者,不能满足多组生产者和消费者的业务场景。

第二个问题就比较棘手了,因为从 List 中 POP 一条消息出来后,这条消息就会立即从链表中删除了。也就是说,无论消费者是否处理成功,这条消息都没办法再次消费了。

这也意味着,如果消费者在处理消息时异常宕机,那这条消息就相当于丢失了。

针对这 2 个问题怎么解决呢?我们一个个来看。

二、发布订阅模式:Pub/Sub

从名字就能看出来,这个模块是 Redis 专门是针对「发布/订阅」这种队列模型设计的。

它正好可以解决前面提到的第一个问题:重复消费。

即多组生产者、消费者的场景,我们来看它是如何做的。

Redis 提供了 PUBLISH / SUBSCRIBE 命令,来完成发布、订阅的操作。
在这里插入图片描述
依赖继续用前面的就行

1、使用RedisMessageListenerContainer实现订阅

  • 通过实现MessageListener接口来处理接收到的消息。这允许您在Spring应用程序中以更高级的方式处理消息,例如使用依赖注入和其他Spring功能。它还支持基于注解的消息监听器,使消息处理更加简洁和灵活。
  • 该方式是Spring Data Redis库提供的方法,用于在Spring应用程序中使用Redis的发布订阅功能。它需要创建一个MessageListenerContainer对象,并通过调用addMessageListener方法来添加消息监听器。
  • 添加监控器,用于监听通道
    为了便于更加直观的对比测试,我添加了两个
/*** @author zhengfuping* @version 1.0* @description: TODO  配置监控器* @date 2023/7/28 17:10*/
@Component
public class RedisMessaeListener1 implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {String channel = new String(message.getChannel());String body = new String(message.getBody());System.out.println("监听器1号:消息: " + body + " 通道QQ: " + channel);}
}/*########################*//*** @author zhengfuping* @version 1.0* @description: TODO 配置监控器1* @date 2023/8/2 11:24*/
@Component
public class RedisMessaeListener2 implements MessageListener {@Overridepublic void onMessage(Message message, byte[] pattern) {String channel = new String(message.getChannel());String body = new String(message.getBody());System.out.println("监听器2号:消息: " + body + " 通道QQ: " + channel);}
}
  • 配置订阅可以单和多个
    用于绑定主题(通道)和监听器,该发送是
/*** @author zhengfuping* @version 1.0* @description: TODO 使用RedisMessageListenerContainer直接注入到bean进行监听* @date 2023/7/28 15:43*/
@Configuration
public class RedisPubSubExample {@Autowiredprivate  RedisMessaeListener1 redisMessaeListener1;@Autowiredprivate  RedisMessaeListener2 redisMessaeListener2;/*** 订阅三个频道* @author zhengfuping* @date 2023/8/2 11:19* @param redisConnectionFactory redis线程工厂* @return RedisMessageListenerContainer*/
//    @Beanpublic RedisMessageListenerContainer subscribeToChannel(RedisConnectionFactory redisConnectionFactory){RedisMessageListenerContainer listenerContainer = new RedisMessageListenerContainer();listenerContainer.setConnectionFactory(redisConnectionFactory);List<Topic> list = new ArrayList<>();list.add(new PatternTopic("TEST01"));list.add(new PatternTopic("TEST02"));list.add(new PatternTopic("TEST03"));/**  redisMessaeListener  消息监听器*  list  订阅的主题(可以单个和多个)*/listenerContainer.addMessageListener(redisMessaeListener1,list);listenerContainer.addMessageListener(redisMessaeListener2,new PatternTopic("TEST01"));return listenerContainer;}
}
  • 向指定频道发送消息
    /***  PUBLISH 发送消息到指定频道* @author zhengfuping* @date 2023/8/2 11:14* @param channel 通道* @param name  数据* @param age* @return Object*/@GetMapping("/pub")public Object pub(String channel,String name,Integer age) {User user = new User(name, age);redisTemplate.convertAndSend(channel,user);return user;}

在这里插入图片描述

2、还可以使用redisTemplate实现订阅

  • 该方式是Redis客户端的方法,用于在独立的Redis客户端中直接使用发布订阅功能。它需要创建一个Redis连接对象,并通过调用subscribe方法来订阅一个或多个频道。

  • 如果您只是在独立的Redis客户端中使用发布订阅功能,并且不需要使用Spring的其他功能,则可以选择connection.subscribe

 /*** 自行添加订阅*/@GetMapping("/sub")public void sub(String channel) {RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();/**  MessageListener:监听器,直接使用内部类实现绑定监听可以把数据传递出去*  channel 订阅频道*/connection.subscribe((message, pattern) -> {String channel1 = new String(message.getChannel());String body = new String(message.getBody());System.out.println("subscribe方式监听:消息: " + body + " 通道QQ: " + channel1);}, channel.getBytes());//            connection.close();}

在这里插入图片描述

发送消息

    @GetMapping("/pub")public Object pub(String channel,String name,Integer age) {User user = new User(name, age);redisTemplate.convertAndSend(channel,user);return user;}

在这里插入图片描述

最终监听到的结果
在这里插入图片描述

三、、 趋于成熟的队列:Stream

我们来看 Stream 是如何解决上面这些问题的。

我们依旧从简单到复杂,依次来看 Stream 在做消息队列时,是如何处理的?

首先,Stream 通过 XADD 和 XREAD 完成最简单的生产、消费模型:

  • 生产者发布 2 条消息:
// *表示让Redis自动生成消息ID
127.0.0.1:6379> XADD queue * name zhangsan
"1618469123380-0"
127.0.0.1:6379> XADD queue * name lisi
"1618469127777-0"
  • 消费者拉取消息:
// 从开头读取5条消息,0-0表示从开头读取
127.0.0.1:6379> XREAD COUNT 5 STREAMS queue 0-0
1) 1) "queue"2) 1) 1) "1618469123380-0"2) 1) "name"2) "zhangsan"2) 1) "1618469127777-0"2) 1) "name"2) "lisi"

流程图
在这里插入图片描述

具体java代码实现:

  • 先配置监听消息类
@Slf4j
@Component
public class ListenerMessage implements StreamListener<String, MapRecord<String, String, String>> {@Overridepublic void onMessage(MapRecord<String, String, String> entries) {log.info("接受到来自redis的消息");System.out.println("message id "+entries.getId());System.out.println("stream "+entries.getStream());System.out.println("body "+entries.getValue());}
}
  • 添加工具类,实现初始化
@Component
@Slf4j
public class RedisStreamUtil {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;/*** @author zhengfuping 添加数据* @param streamKey* @param map* @return RecordId*/public RecordId addStream(String streamKey,Map<String, Object> map){RecordId recordId = redisTemplate.opsForStream().add(streamKey, map);return recordId;}/*** 用来创建绑定流和组*/public void addGroup(String key, String groupName){redisTemplate.opsForStream().createGroup(key,groupName);}/*** 用来判断key是否存在*/public boolean hasKey(String key){if(key==null){return false;}else{return redisTemplate.hasKey(key);}}/*** 用来删除掉消费了的消息*/public void delField(String key,String recordIds){redisTemplate.opsForStream().delete(key,recordIds);}/*** 用来初始化 实现绑定*/public void initStream(String key, String group){//判断key是否存在,如果不存在则创建boolean hasKey = hasKey(key);if(!hasKey){Map<String,Object> map = new HashMap<>();map.put("field","value");RecordId recordId = addStream(key, map);addGroup(key,group);   //把Stream和gropu绑定delField(key,recordId.getValue());log.info("stream:{}-group:{} initialize success",key,group);}}
}
  • 添加配置类,配置Stream
/*** @author zhengfuping* @version 1.0* @description: TODO  添加配置类,配置Stream*/
@Configuration
@Slf4j
public class RedisStreamConfig {@Autowiredprivate RedisStreamUtil redisStream;@Autowiredprivate ListenerMessage listenerMessage;@Beanpublic Subscription subscription(RedisConnectionFactory factory){
//        代码中的var是使用了Lombok的可变局部变量。主要是为了方便
//        StreamMessageListenerContainer: 消息侦听容器,不能在外部实现。创建后,StreamMessageListenerContainer可以订阅Redis流并使用传入的消息var options = StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder().pollTimeout(Duration.ofSeconds(1)).build();redisStream.initStream("mystream","mygroup");	//调用初始化var listenerContainer = StreamMessageListenerContainer.create(factory,options);/**  注意这里接受到消息后会被自动的确认,如果不想自动确认请使用其他的创建订阅方式* 消费组 consumer group ,它不能为null (Consumer类型)* stream offset ,stream的偏移量(StreamOffset 类型)* listener 不能为null (StreamListener<K,V> 类型)*/var subscription = listenerContainer.receiveAutoAck(Consumer.from("mygroup","huhailong"),StreamOffset.create("mystream", ReadOffset.lastConsumed()),listenerMessage);listenerContainer.start();return subscription;}
}
  • 调用测试
/*** @author zhengfuping* @version 1.0* @description: TODO* @date 2023/8/2 16:06*/
@RestController
@RequestMapping("/redisStream")
public class RedisStreamTest {@Autowiredprivate RedisStreamUtil redisStream;@GetMapping("add")public void add(String key,String data){Map<String, Object> map = new HashMap<>();map.put(key,data);
//        添加数据到mystream流中RecordId recordId = redisStream.addStream("mystream", map);
//        删除流中消费了的指定key的数据redisStream.delField("mystream",recordId.getValue());}
}

Stream的好处在于可以写入到 RDB 和 AOF 做持久化。
Stream是新增加的数据类型,它与其它数据类型一样,每个写操作,也都会写入到 RDB 和 AOF 中。
我们只需要配置好持久化策略,这样的话,就算 Redis 宕机重启,Stream 中的数据也可以从 RDB 或 AOF 中恢复回来。

总结

好了,总结一下。这篇文章我们从「Redis 能否用作队列」这个角度出发,介绍了 List、Pub/Sub、Stream 在做队列的使用方式,以及它们各自的优劣。

之后又把 Redis 和专业的消息队列中间件做对比,发现 Redis 的不足之处。

最后,我们得出 Redis 做队列的合适场景。

在这里插入图片描述

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

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

相关文章

小白到运维工程师自学之路 第六十二集 (docker持久化与数据卷容器)

一、概述 Docker持久化是指将容器中的数据持久保存在主机上&#xff0c;以便在容器重新启动或迁移时不丢失数据。由于Docker容器是临时和可变的&#xff0c;它们的文件系统默认是易失的&#xff0c;这意味着容器中的任何更改或创建的文件都只存在于此容器的生命周期内。但是&a…

小主机折腾记16

7月折腾了 1.2500s&#xff0c;2550k&#xff0c;e3 1225的性能测试 结果如下图 总结如下&#xff1a; a.2500s e3 1225 2390t 差别不大 b.1333频率相对1066频率内存提升12%左右 c.为什么少了2550k&#xff0c;因为装上去风扇尬转&#xff0c;没画面&#xff0c;我猜是因为…

node.js判断元素是否包括

在Node.js中&#xff0c;可以使用Array.prototype.some()方法来判断数组中是否包含某个元素。下面是一个示例代码&#xff1a; const arr [ { ‘_android:name’: ‘com.eg.android.AlipayGphone’ }, { ‘_android:name’: ‘com.eg.android.AlipayGphoneRC’ }, { ‘_andro…

助力青少年科技创新人才培养,猿辅导投资1亿元设立新基金

近日&#xff0c;在日本千叶县举办的2023年第64届国际数学奥林匹克&#xff08;IMO&#xff09;竞赛公布比赛结果&#xff0c;中国队连续5年获得团体第一。奖牌榜显示&#xff0c;代表中国参赛的6名队员全部获得金牌。其中&#xff0c;猿辅导学员王淳稷、孙启傲分别以42分、39分…

用latex的ACM模板写论文如何去除页眉页脚以及Reference

简单粗暴如下&#xff1a; 1、latex最开始补充&#xff1a; \documentclass[acmsmall]{acmart} \settopmatter{printacmreffalse} % Removes citation information below abstract \renewcommand\footnotetextcopyrightpermission[1]{} % removes footnote with conference in…

FFmepg视频解码

1 前言 上一篇文章<FFmpeg下载安装及Windows开发环境设置>介绍了FFmpeg的下载安装及环境配置&#xff0c;本文介绍最简单的FFmpeg视频解码示例。 2 视频解码过程 本文只讨论视频解码。 FFmpeg视频解码的过程比较简单&#xff0c;实际就4步&#xff1a; 打开媒体流获取…

代码随想录额外题目| 二叉树 ●129求根到叶数字之和 ●1382二叉树变平衡●100相同的树

#129求根到叶数字之和 回溯放进vector&#xff0c;然后从后往前拿&#xff0c;乘1 10 100 ... 很基础的回溯 my code&#xff1a; void backtrack(int depth, TreeNode* cur, vector<TreeNode*> &vec, int &sum){if(cur->leftnullptr &&cur->rig…

如何方便地使用TCL恢复带BD设计的Vivado工程

恢复无BD设计的Vivado工程 当工程中无Block Design设计时&#xff0c;工程恢复过程相对简单。使用write_project_tcl命令可以直接生成用于恢复工程的tcl文件&#xff0c;如*_prj.tcl&#xff0c;在恢复时直接运行就可以了。 修改恢复工程的路径 *_prj.tcl在恢复工程时会将其…

监控对象都有哪些分类

1、业务监控 这类指标是管理层非常关注的&#xff0c;代表企业营收&#xff0c;或者跟客户主流程相关&#xff0c;类似 BI 数据。不过相比 BI 数据&#xff0c;业务监控指标有两点不同。 对精确度要求没有那么高&#xff1a;因为监控只要发现趋势异常就可以&#xff0c;至于是…

极简在线商城系统,支持docker一键部署

Hmart 给大家推荐一个简约自适应电子商城系统&#xff0c;针对虚拟商品在线发货&#xff0c;支持企业微信通知&#xff0c;支持docker一键部署&#xff0c;个人资质也可搭建。 前端 后端 H2 console 运行命令 docker run -d --name mall --restartalways -p 8080:8080 -e co…

LeetCode_贪心算法_中等_763.划分字母区间

目录 1.题目2.思路3.代码实现&#xff08;Java&#xff09; 1.题目 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍…

Jackson:String转object反序列化失败

场景 消费mq时String转Object 代码 for (MessageExt msg : msgs) {String msgBody new String(msg.getBody(), StandardCharsets.UTF_8);BinlogEvent binlogEvent JsonUtil.silentString2Object(msgBody, BinlogEvent.class);binlogEvent.setPort(Long.valueOf(port));tCo…

SAP CAP篇十二:AppRouter 深入研究

本文目录 本系列文章理解现有程序app文件夹中的package.json理解approuter.js 修改现有程序修改package.json新建index.js在Approuter中显示额外的逻辑 添加一些额外的Logger对应代码及branch 本系列文章 SAP CAP篇一: 快速创建一个Service&#xff0c;基于Java的实现 SAP CAP…

深入浅出Pytorch函数——torch.sum

分类目录&#xff1a;《深入浅出Pytorch函数》总目录 相关文章&#xff1a; 深入浅出Pytorch函数——torch.Tensor 函数torch.sum有两种形式&#xff1a; torch.sum(input, *, dtypeNone)&#xff1a;返回输入张量input所有元素的和。torch.sum(input, dim, keepdimFalse, *,…

2308C++技巧

struct ubiq {template <class Type>constexpr operator Type() const {return Type{};}; }; // int i ubiq{}; double d ubiq{}; char c ubiq{}; //可以多个同时初化. template <class T, std::size_t... I> constexpr auto 类型转标识数组(std::size_t* types…

(AcWing)多重背包问题 I,II

有 N 种物品和一个容量是 V 的背包。 第 i 种物品最多有 si 件&#xff0c;每件体积是 vi&#xff0c;价值是 wi。 求解将哪些物品装入背包&#xff0c;可使物品体积总和不超过背包容量&#xff0c;且价值总和最大。 输出最大价值。 输入格式 第一行两个整数 N&#xff0c;…

使用树莓派picow和drv8833驱动直流电机

raspberry pico w引脚图 1. 准备工作 板子编辑器raspberry pico wmicropython&#xff08;thonny编辑器&#xff09; 最新的raspberry pi pico w系统包下载地址。 点亮板载led灯 需要注意的是pico的板载led灯是GPIO25引脚&#xff0c;picow的板子led灯则直接用Pin包的&qu…

Spring依赖注入Bean属性

目录 1.依赖注入 1.1构造 1.2set注入 2.基于XML的自动装配 2.1根据名称: 2.2根据类型: 2.3根据构造自动注入 3.Spring 文件中 Bean属性 1.依赖注入 依赖注入&#xff0c;是IOC的一个方面&#xff0c;是个通常的概念&#xff0c;它有多种解释。这概念是说你不用创建对象&…

Unity 使用SharpZipLib解压时报错

报错信息&#xff1a; NotSupportedException: Encoding 936 data could not be found. Make sure you have correct international System.Text.Encoding.GetEncoding (System.Int32 codepage) ICSharpCode.SharpZipLib.Zip.ZipConstants.ConvertToString。 出现问题分析&…

【leetcode】138.复制带随机指针的链表

方法一&#xff1a;暴力求解 1️⃣遍历原链表&#xff0c;复制节点尾插 2️⃣更新random&#xff0c;原链表中的random对应第几个节点则复制链表中的random就对应第几个 &#x1f4d6;Note 不能通过节点中的val判断random的指向&#xff0c;因为链表中可能存在两个val相等的节点…