2024.2.27 模拟实现 RabbitMQ —— 网络通信设计(客户端)

news2024/10/3 2:22:53

目录

需求分析

RabbitMQ 客户端设定

ConnectionFactory(连接工厂)

Connection(连接)

Channel(通道)

针对 客户端 和 服务器 单元测试


需求分析

RabbitMQ 客户端设定

  • 一个客户端可以有多个模块
  • 每个模块均可以和 Broker Server 之间建立 "逻辑上的连接"(channel)
  • 这几个模块的 channel 彼此之间是相互不影响的
  • 同时这几个 channel 复用了同一个 TCP 连接
  • 此处我们将仿照 RabbitMQ 客户端设定

ConnectionFactory(连接工厂)

  • 这个类持有服务器的地址
  • 该类用于创建 Connection 对象

具体代码编写:

import lombok.Getter;
import lombok.Setter;

import java.io.IOException;

@Getter
@Setter
public class ConnectionFactory {
//    broker server 的 ip 地址
    private String host;
//    broker server 的端口号
    private int port;

    public Connection newConnection() throws IOException {
        Connection connection = new Connection(host,port);
        return connection;
    }

//    访问 broker server 的哪个虚拟主机
//    下列几个属性暂时先不搞了
//    private String virtualHostName;
//    private String username;
//    private String password;
}

Connection(连接)

  • 这个类表示一个 TCP 连接,持有 Socket 对象
  • 该类用于写入请求/读取响应,管理多个 Channel 对象

具体代码编写:

  • 编写成员变量
    private Socket socket = null;
//    需要管理多个 channel 使用一个 hash 表把若干个 channel 组织起来
    private ConcurrentHashMap<String,Channel> channelMap = new ConcurrentHashMap<>();

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataOutputStream dataOutputStream;
    private DataInputStream dataInputStream;

//用来处理 0xc 的回调,这里开销可能会很大,不希望把 扫描线程 阻塞住,因此使用 线程池 来处理
    private ExecutorService callbackPool = null;
  • 编写构造方法
  • 此处不仅需要初始化成员变量,还需创建一个扫描线程,不停的从 socket 中读取响应数据,并将读取到的响应交给 dispatchResponse 方法执行
 public Connection(String host, int port) throws IOException {
        socket = new Socket(host,port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);

        callbackPool = Executors.newFixedThreadPool(4);

//        创建一个 扫描线程,由这个线程负责不停的从 socket 中取响应数据 把这个响应数据再交给对应的 channel 负责处理
        Thread t = new Thread(() -> {
            try {
                while (!socket.isClosed()) {
                    Response response = readResponse();
                    dispatchResponse(response);
                }
            }catch (SocketException e) {
//                连接正常断开,此时这个异常直接忽略
                System.out.println("[Connection] 连接正常断开!");
            }catch (IOException | ClassNotFoundException | MqException e) {
                System.out.println("[Connection] 连接异常断开!");
                e.printStackTrace();
            }
        });
        t.start();
    }
  • 编写 dispatchResponse 方法
  • 使用该方法来区分,当前响应是一个针对控制请求的响应,还是服务器推送过来的消息
  • 如果是服务器推送过来的消息,type = 0xc,也就需要执行回调,通过线程池来执行
  • 如果只是一个普通的响应,就将该结果放到 channel 的 哈希表中
  • 随后 channel 的 putReturns 方法会唤醒所有阻塞等待的线程,让这些线程从 哈希表中拿与自己 rid 相等的返回结果
//    使用这个方法来分别处理,当前的响应是一个针对控制请求的响应,还是服务器推送的消息
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if(response.getType() == 0xc) {
//            服务器推送来的消息数据
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
//            根据 channelId 找到对应的 channel 对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if(channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在!channelId = " + channel.getChannelId());
            }
//            执行该 channel 对象内部的回调
//            此处我们直接将回调方法交给线程池来执行,而不是用扫描线程来执行
            callbackPool.submit(() -> {
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(),subScribeReturns.getBasicProperties(),
                            subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    e.printStackTrace();
                }
            });
        }else {
//            当前响应是针对刚才控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
//            把这个结果放到对应的 channel 的 hash 表中
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if(channel == null) {
                throw new MqException("[Connection] 该消息对应的 channel 在客户端中不存在!channelId = " + channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }
  • 编写 发送请求 与 读取响应 的方法
//    发送请求
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求! type = " + request.getType() + ",length = " + request.getLength());
    }

//    读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if(n != payload.length) {
            throw new IOException("读取的响应数据不完整!");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应! type = " + response.getType() + ",length = " + response.getLength());
        return response;
    }
  • 编写创建 channel 的方法

注意:

  • 我们的代码中使用了多次 UUID 
  • message 的 id,就是用 UUID 当时加了个 M- 前缀
  • 现在 channel 的 id 也是使用 UUID 此时加个 C- 前缀
  • rid 也使用 UUID 来生成,加个前缀  R-
//    通过这个方法,在 Connection 中能够创建出一个 Channel
    public Channel createChannel() throws IOException, InterruptedException {
        String channelId = "C-" + UUID.randomUUID().toString();
        Channel channel = new Channel(channelId,this);
//        把这个 channel 对象放到 Connection 管理 channel 的 哈希表 中
        channelMap.put(channelId,channel);
//        同时也需要把 创建 channel 的这个消息也告诉服务器
        boolean ok = channel.createChannel();
        if(!ok) {
//            服务器这里创建失败了!!整个这次创建 channel 操作不顺利!
//            把刚才已经加入 hash 表的键值对,再删了
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
  • 编写释放 Connection 相关资源的方法
    public void close() {
//        关闭 Connection 释放上述资源
        try {
            callbackPool.shutdownNow();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

Channel(通道)

  • 这个类表示一个逻辑上的连接

  • 该类用于提供一系列的方法,去和服务器提供的核心 API 相对应

  • 客户端提供的这些方法,其方法内部就是发送了一个特定的请求

具体代码编写:

  • 编写成员变量  与 构造方法
    private String channelId;
//    当前这个 channel 属于哪个连接的
    private Connection connection;
//    用来存储后续客户端收到的服务器的响应
    private ConcurrentHashMap<String, BasicReturns> basicReturnsMap = new ConcurrentHashMap<>();
//    如果当前 Channel 订阅了某个队列,就需要在此处记录下对应回调是啥,当该队列的消息返回回来的时候,调用回调
//    此处约定一个 Channel 中只能有一个回调
    private Consumer consumer = null;

    public Channel(String channelId,Connection connection) {
        this.channelId = channelId;
        this.connection = connection;
    }
  • 实现 type = 0x1,即创建 channel
  • 构造请求发给服务器,随后阻塞等待,唤醒后从 basicReturnsMap 中尝试获取响应结果
  • 其余 type (0xc 除外,因为 0xc 只有响应没有请求)类型的请求与 0x1 大差不差,对着所需参数,构造即可
//    在这个方法中,和服务器进行交互,告知服务器,此处客户端创建了新的 channel 了
    public Boolean createChannel() throws IOException, InterruptedException {
//        对于创建 Channel 来说,
        BasicArguments basicArguments = new BasicArguments();
        basicArguments.setChannelId(channelId);
        basicArguments.setRid(generateRid());
        byte[] payload = BinaryTool.toBytes(basicArguments);

        Request request = new Request();
        request.setType(0x1);
        request.setLength(payload.length);
        request.setPayload(payload);

//        构造出完整请求之后,就可以发送这个请求了
        connection.writeRequest(request);
//        等待服务器的响应
        BasicReturns basicReturns = waitResult(basicArguments.getRid());
        return basicReturns.isOk();
    }

    private String generateRid() {
        return "R-" + UUID.randomUUID().toString();
    }

//    期望使用这个方法来阻塞等待服务器的响应
    private BasicReturns waitResult(String rid) throws InterruptedException {
        BasicReturns basicReturns = null;
        while ((basicReturns = basicReturnsMap.get(rid)) == null) {
//            如果查询结果为 null,说明响应还没回来
//            此时就需要阻塞等待
            synchronized (this) {
                wait();
            }
        }
//        读取成功之后,还需要把这个消息从 哈希表中给删除掉
        basicReturnsMap.remove(rid);
        System.out.println("[Channel] 获取到服务器响应!rid = " + rid);
        return basicReturns;
    }

    public void putReturns(BasicReturns basicReturns) {
        basicReturnsMap.put(basicReturns.getRid(),basicReturns);
        synchronized (this) {
//            当前也不知道有多少个线程在等待上述的这个响应
//            把所有的等待的线程都唤醒
            notifyAll();
        }
    }
  • 特别注意 type = 0xa ,即 订阅消息

  • 其次值得注意的是 consumerTag 使用 channelId 来表示
//    订阅消息
    public boolean basicConsume(String queueName, boolean autoAck, Consumer consumer) throws MqException, IOException, InterruptedException {
//        先设置回调
        if(this.consumer != null) {
            throw new MqException("该 channel 已经设置过消费消息的回调了,不能重复设置!");
        }
        this.consumer = consumer;
        BasicConsumeArguments basicConsumeArguments = new BasicConsumeArguments();
        basicConsumeArguments.setRid(generateRid());
        basicConsumeArguments.setChannelId(channelId);
//        此处 ConsumerTag 也使用 channelId 来表示
        basicConsumeArguments.setConsumerTag(channelId);
        basicConsumeArguments.setQueueName(queueName);
        basicConsumeArguments.setAutoAck(autoAck);
        byte[] payload = BinaryTool.toBytes(basicConsumeArguments);

        Request request = new Request();
        request.setType(0xa);
        request.setLength(payload.length);
        request.setPayload(payload);

        connection.writeRequest(request);
        BasicReturns basicReturns = waitResult(basicConsumeArguments.getRid());
        return basicReturns.isOk();
    }

针对 客户端 和 服务器 单元测试

  • 编写测试用例代码是十分重要的!
package com.example.demo;

import com.example.demo.common.Consumer;
import com.example.demo.common.MqException;
import com.example.demo.mqclient.Channel;
import com.example.demo.mqclient.Connection;
import com.example.demo.mqclient.ConnectionFactory;
import com.example.demo.mqserver.BrokerServer;
import com.example.demo.mqserver.core.BasicProperties;
import com.example.demo.mqserver.core.ExchangeType;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;

import java.io.File;
import java.io.IOException;

public class MqClientTests {
    private BrokerServer brokerServer = null;
    private ConnectionFactory connectionFactory = null;
    private Thread t = null;

    @BeforeEach
    public void setUp() throws IOException {
//        1、先启动服务器
        DemoApplication.context = SpringApplication.run(DemoApplication.class);
        brokerServer = new BrokerServer(9090);
        t = new Thread(() -> {
//        这个 start 方法会进入一个死循环,使用一个新的线程来运行 start 即可!
            try {
                brokerServer.start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        t.start();

//        2、配置 ConnectionFactory
        connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(9090);

    }

    @AfterEach
    public void tearDown() throws IOException {
//        停止服务器
        brokerServer.stop();
//        t.join();
        DemoApplication.context.close();

//        删除必要的文件
        File file = new File("./data");
        FileUtils.deleteDirectory(file);

        connectionFactory = null;
    }

    @Test
    public void testConnection() throws IOException {
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
    }

    @Test
    public void testChannel() throws IOException, InterruptedException {
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
        Channel channel = connection.createChannel();
        Assertions.assertNotNull(channel);
    }

    @Test
    public void testExchange() throws IOException, InterruptedException {
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
        Channel channel = connection.createChannel();
        Assertions.assertNotNull(channel);

        boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
        Assertions.assertTrue(ok);

        ok = channel.exchangeDelete("testExchange");
        Assertions.assertTrue(ok);

//        此处稳妥起见,把该关闭的要进行关闭
        channel.close();
        connection.close();
    }

    @Test
    public void testQueue() throws IOException, InterruptedException{
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
        Channel channel = connection.createChannel();
        Assertions.assertNotNull(channel);

        boolean ok = channel.queueDeclare("testQueue",true,false,false,null);
        Assertions.assertTrue(ok);

        ok = channel.queueDelete("testQueue");
        Assertions.assertTrue(ok);

        channel.close();
        connection.close();
    }

    @Test
    public void testBinding() throws IOException, InterruptedException{
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
        Channel channel = connection.createChannel();
        Assertions.assertNotNull(channel);

        boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
        Assertions.assertTrue(ok);

        ok = channel.queueDeclare("testQueue",true,false,false,null);
        Assertions.assertTrue(ok);

        ok = channel.queueBind("testQueue","testExchange","testBindingKey");
        Assertions.assertTrue(ok);

        ok = channel.queueUnbind("testQueue","testExchange");
        Assertions.assertTrue(ok);

        channel.close();
        connection.close();
    }

    @Test
    public void testMessage() throws IOException, InterruptedException, MqException {
        Connection connection = connectionFactory.newConnection();
        Assertions.assertNotNull(connection);
        Channel channel = connection.createChannel();
        Assertions.assertNotNull(channel);

        boolean ok = channel.exchangeDeclare("testExchange", ExchangeType.DIRECT,true,false,null);
        Assertions.assertTrue(ok);

        ok = channel.queueDeclare("testQueue",true,false,false,null);
        Assertions.assertTrue(ok);

        byte[] requestBody = "hello".getBytes();
        ok = channel.basicPublish("testExchange","testQueue",null,requestBody);
        Assertions.assertTrue(ok);

        channel.basicConsume("testQueue", true, new Consumer() {
            @Override
            public void handleDelivery(String consumerTag, BasicProperties basicProperties, byte[] body) throws MqException, IOException {
                System.out.println("[消费数据] 开始!");
                System.out.println("consumeTag = " + consumerTag);
                System.out.println("basicProperties = " + basicProperties);
                Assertions.assertArrayEquals(requestBody,body);
                System.out.println("[消费数据] 结束!");
            }
        });
        Assertions.assertTrue(ok);

        Thread.sleep(500);

        channel.close();
        connection.close();
    }
}

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

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

相关文章

揭秘随身WiFi行业背后的创业故事:格行如何成为领头羊?

第一&#xff0c;聚焦蓝海市场。与其在红海市场与众多对手激烈竞争&#xff0c;不如寻找被忽视的市场需求。格行随身WiFi项目就是一个典型的例子&#xff0c;市场需求量大&#xff0c;比如网约车&#xff0c;外卖员&#xff0c;户外直播&#xff0c;租房党&#xff0c;经常外出…

苹果Vision Pro芯片级拆解:339S01186传感器协处理器,电源管理芯片:343S00627、343S00628、343S00629

2月2日&#xff0c;苹果官方发售Vision Pro后&#xff0c;引发了抢购潮&#xff0c;产品“秒光”&#xff0c;甚至将起售价 3499 美元一度炒到近 9 万元人民币的代购价。 日前知名维修网站iFixit又一次“首拆”了苹果Vision Pro&#xff0c;每一块电路板、每一颗螺丝钉、每一颗…

游泳耳机怎么选?2024年必看的四款王牌游泳耳机推荐!

游泳不仅能有效锻炼身体&#xff0c;还能帮助我们在炎炎夏日中消暑解热。而随着科技的发展&#xff0c;越来越多的智能设备开始融入我们的生活&#xff0c;其中就包括了专门为游泳爱好者设计的游泳耳机。在水下也能享受到音乐的乐趣&#xff0c;无疑为游泳这项运动增添了更多的…

petalinux_zynq7 驱动DAC以及ADC模块之五:nodejs+vue3实现web网页波形显示

前文&#xff1a; petalinux_zynq7 C语言驱动DAC以及ADC模块之一&#xff1a;建立IPhttps://blog.csdn.net/qq_27158179/article/details/136234296petalinux_zynq7 C语言驱动DAC以及ADC模块之二&#xff1a;petalinuxhttps://blog.csdn.net/qq_27158179/article/details/1362…

基于AMDGPU-ROCm的深度学习环境搭建

在风起云涌的AI江湖&#xff0c;NVIDIA凭借其CUDA生态和优秀的硬件大杀四方&#xff0c;立下赫赫战功&#xff0c;而另一家公司AMD也不甘示弱&#xff0c;带着他的生态解决方案ROCm开始了与不世出的NVIDA的正面硬钢&#xff0c;"ROCm is the answer to CUDA", AMD官网…

MySQL锁三部曲:临键、间隙与记录的奇妙旅程

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 MySQL锁三部曲&#xff1a;临键、间隙与记录的奇妙旅程 前言临键锁的奥秘间隙锁记录锁 前言 在数据库世界中&#xff0c;锁是维护数据完整性的一种关键机制。而MySQL中的临键锁、间隙锁和记录锁则是锁…

2024.2.25 -ElasticSearch 进阶

倒排索引 Elasticsearch的倒排索引机制是通过将文档中出现的词汇与它们所在的文档ID关联起来&#xff0c;实现快速查找包含特定词汇的文档。下面是一个具体的例子来说明倒排索引的工作原理&#xff1a; 假设我们有一个简单的文章集合&#xff0c;包含以下三篇文章&#xff1a…

pytorch 用F.normalization的逆归一化如何操作

逆归一化的时候再把这个数乘回去就行了 magnitude a.norm(p2, dim1, keepdimTrue) # NEW atorch.nn.functional.normalize(a, p2, dim1) a_or a* magnitude # NEW print(a_or) Outputs: tensor([]1,2,3)

springboot网站开发02-接入持久层框架mybatisPlus

springboot网站开发02-接入持久层框架mybatisPlus&#xff01;经过上一小节内容分享&#xff0c;我们的项目嵌套模式框架搭建好了&#xff0c;下面就是开始编辑具体的业务代码了&#xff0c;我们使用到了持久层框架是mybatisPlus插件。下面是一些具体的植入框架的操作步骤。 第…

【Java程序员面试专栏 算法思维】二 高频面试算法题:二分查找

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,本篇主要聊聊二分查找,包括基础二分,寻找目标值的左右边界,搜索旋转数组以及波峰,以及x的平方根问题,所以放到一篇Blog中集中练习 题目关键字解题思路时间空…

【MySQL面试复习】索引创建的原则有哪些?

系列文章目录 在MySQL中&#xff0c;如何定位慢查询&#xff1f; 发现了某个SQL语句执行很慢&#xff0c;如何进行分析&#xff1f; 了解过索引吗&#xff1f;(索引的底层原理)/B 树和B树的区别是什么&#xff1f; 什么是聚簇索引&#xff08;聚集索引&#xff09;和非聚簇索引…

MAC M1 安装mongodb7.0.5 版本

1、进入官网 Download MongoDB Community Server | MongoDBDownload MongoDB Community Server non-relational database to take your next big project to a higher level!https://www.mongodb.com/try/download/community 2、选择版本 3、下载后解压 放到 /usr/local 并修改…

好狄空气能热水器成功上榜2023年消费者心中的十大信赖品牌

随着环保意识的增强和能源消耗的持续关注&#xff0c;空气能热水器以其高效节能、绿色环保的特性赢得了越来越多消费者的青睐。市场上琳琅满目的空气能热水器品牌让消费者在选择时既兴奋又困惑。究竟哪些品牌能在激烈的竞争中脱颖而出&#xff0c;成为消费者心目中的佼佼者呢&a…

MongoDB之客户端工具与核心概念及基本类型篇

MongoDB之客户端工具与核心概念及基本类型篇 文章目录 MongoDB之客户端工具与核心概念及基本类型篇1. MongoDB是什么?1. 关于MongoDB2. 相关客户端工具1. MongoDB Compass2. Studio 3T3. Navicat for MongoDB4. NoSQL Manager for MongoDB Professional 2.MongoDB相关概念2.1 …

RabbitMQ实战学习

RabbitMQ实战学习 文章目录 RabbitMQ实战学习RabbitMQ常用资料1、安装教程2、使用安装包3、常用命令4、验证访问5、代码示例 一、RabbitMQ基本概念1.1. MQ概述1.2 MQ 的优势和劣势1.3 MQ 的优势1. 应用解耦2. 异步提速3. 削峰填谷 1.4 MQ 的劣势1.5 RabbitMQ 基础架构1.6 JMS 二…

钡铼lora智能网关终端节点温湿度无线采集4G远传

钡铼LoRa智能网关终端节点是一种用于温湿度无线采集和4G远传的设备&#xff0c;它能够实现远程监测和数据传输&#xff0c;适用于各种应用场景&#xff0c;包括工业、农业、环境监测等领域。在设置钡铼LoRa智能网关终端节点时&#xff0c;我们需要考虑到设备的功能特点、网络连…

C++ 入门(六)— 调试程序(Debugging)

文章目录 语法和语义错误调试(Debugging)调试过程调试策略常用的调试策略更多调试策略 使用集成调试器步进&#xff08;Stepping&#xff09; 语法和语义错误 语法错误 编写根据 C 语言的语法无效的语句时&#xff0c;会发生语法错误。例如缺少分号、使用未声明的变量、括号或…

字符函数和字符串函数(C语言进阶)(三)

目录 前言 接上篇&#xff1a; 1.7 strtok 1.8 strerror 1.9 字符分类函数 总结 前言 C语言中对字符和字符串的处理是很频繁的&#xff0c;但是c语言本身是没有字符串类型的&#xff0c;字符串通常放在常量字符串中或着字符数组中。 字符串常量适用于那些对它不做修改的字…

生成式 AI - Diffusion 模型的数学原理(5)

来自 论文《 Denoising Diffusion Probabilistic Model》&#xff08;DDPM&#xff09; 论文链接&#xff1a; https://arxiv.org/abs/2006.11239 Hung-yi Lee 课件整理 讲到这里还没有解决的问题是&#xff0c;为什么这里还要多加一个噪声。Denoise模型算出来的是高斯分布的均…

根据前序后序遍历求出二叉树

根据前序后序遍历求出二叉树 一、题目描述 给定两个整数数组&#xff0c;preorder 和 postorder &#xff0c;其中 preorder 是一个具有 无重复 值的二叉树的前序遍历&#xff0c;postorder 是同一棵树的后序遍历&#xff0c;重构并返回二叉树。 二、题目分析 需求&#xff…