SpringAMQP (RabbitMQ五种模式 消息转换器)

news2024/11/20 3:26:49

一、简化Hello World模型实现步骤演示

 代码步骤演示如下所示:

消息提供者publisher代码简化:

消息消费者consumer代码简化:

二、Work Queue 工作队列模型

消息提供者代码如下所示:

消息消费者代码如下所示:

消息预取机制: 

三、发布订阅模型

而订阅模型是什么呢:

3.1、FanoutExchange(广播交换机)的使用

具体java代码演示如下所示:

第一步:

第二步: 

第三步:

结论:

3.2、DirectExchange(路由交换机)模式

代码演示上面过程如下所示:

 第一步:使用@RabbitListener注解声明交换机、队列、BindingKey

演示1:

 演示2:

总结:(面试用)

3.3、TopicExchange(发布订阅)交换机模型

 代码演示如下所示:

四、SpringAMQP消息转换器 ****

问题演示如下所示:

分析原因:

因此解决这种我们发送的对象消息被序列化的问题,我们就需要使用消息转换器了: 


什么是SpringAAMQP:        

        一句话,就是简化刚才消息提供者/消费者向MQ队列发送/订阅获取消息的代码的(我们前面的笔记知道,那些代码步骤太杂了,还要创建连接,创建通道等......)

一、简化Hello World模型实现步骤演示

        就是简化刚才我们写的那个消息提供者向RabbitMQ消息队列发送消息,和消息消费者向RabbitMQ消息队列中订阅获取暂存在RabbitMQ消息队列中的消息代码 (简化建立联系、创建通道等那些代码)

 代码步骤演示如下所示:

消息提供者publisher代码简化:

第一步:

 <!--AMQP依赖,包含RabbitMQ-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

第二步:

 

操作如下所示:  

 消息发送之后,就会发现我们simple.queue队列中有了这个hello,spring amqp! 消息了:

值得注意的是:如果没有这个simple.queue队列的话,那么我们消息发送者向simple.queue队列中发送消息是发送不成功的,不会给我们自动新建simple.queue队列。

消息消费者consumer代码简化:

        同理第一步也是先看看父工程中是否导入了SpringAMQP的依赖。

第二步:

具体代码演示如下所示:

 

然后运行我们的消息消费者启动类,会发现确实拿到了RabbitMQ消息队列中的消息了:

 

二、Work Queue 工作队列模型

演示如下所示: 

消息提供者代码如下所示:

@Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     *      回忆基础: 首先我们要知道 Thread.sleep(20); 中 20对应的是毫秒, 换算单位:20毫秒 = 0.02秒 ( 毫秒 × 1000 = 秒 )
     *
     *      我们下面知道循环50次向队列中发送消息,并且每发送一条消息睡眠0.02秒,那么【理论】上我们消息提供者
     *      向队列中发送完这50次消息需要耗时1s (也就是说一秒中把这五十条消息发送到队列当中去)
     *
     */
    @Test
    public void testSendMessage2WorkQueue() throws InterruptedException {
        String queueName = "simple.queue";    // 也就是说消息发送者向这个simple.queue队列中发送下面的消息
        String message = "hello, message__";
        for (int i = 1; i <= 50; i++) {
            rabbitTemplate.convertAndSend(queueName, message + i);  // 把 message + i 消息发送到simple.queue队列当中
            Thread.sleep(20);
        }
    }

最终我们会发现simple.queue队列中确实暂存了消息提供者发送的50条消息了:

消息消费者代码如下所示:

@Component
public class SpringRabbitListener {

    /**
     *  假设这个是消费者1:
     *      消费者1每0.02s消费simple.queue队列中一条消息
     *
     *      (队列中50条消息的话理论上消费者1用1s的时间就能消费完了,理论上说
     *        消息提供者每1s发送50条消息,而消费者1每1s消费50条消息,理论上队列中不会堆积消息了)
     */
    @RabbitListener(queues = "simple.queue")    // 也就是说这个消息消费者就消费这个simple.queue队列中的消息数据
    public void listenWorkQueue1(String msg) throws InterruptedException {
        System.out.println("消费者1接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(20);
    }


    /**
     * 假设这个是消费者2:
     *    消费者2每0.2s才消费simple.queue队列中的一条消息,就算是1s钟也就才消费了队列中5条消息
     *
     *    也就是说我们现在simple.queue队列中有50条消息,按理说这50条消息,消费者1消费的快,那么消费者1就应该
     *     打印获取更多的队列中的消息吧,毕竟比消费者2消费的快,能者多劳嘛。
     */
    @RabbitListener(queues = "simple.queue")    // 也就是说这个消息消费者就消费这个simple.queue队列中的消息数据
    public void listenWorkQueue2(String msg) throws InterruptedException {
        System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalTime.now());
        Thread.sleep(200);
    }
}

我们从上面的代码中可以知道,消费者1每0.02秒就能消费一条队列中的消息,而消费者2每0.2秒才消费队列中一条消息,那么消费者1明显比消费者2消费的快,现在我们消息队列中

知道了已经暂存了50条消息了,按理说我们消费者1拿到的消息数据肯定会比消费者2拿到的多。 

我们直接开启两个消息消费者进行消费测试(注意:先开启消息消费者,消息提供者再向队列中发送消息,要不然这五十条消息全被消费者1消费掉了): 

 会发现好家伙,消费者1的消费能力明明比消费者2的能力高的多,但这两个消息消费者还是平均分配这队列中的50条消息了,这是怎么回事呢,这就是消息预取机制

消息预取机制: 

        也就是说上面本来消息消费者1的能力比消息消费者2的消费能力高,但是这两个消息消费者同时消费队列中暂存的消息的时候却还是平均分配消费了,这就是消息预取机制的作用了。 

什么是消息预取机制呢: 

现在我们假如有这样一个问题:就是能者多劳,明明消息消费者1的消费队列中的消息能力高于消息消费者2的消费能力,消费者1说了凭什么我消费能力比他强还让我们俩个平均消费消息呢,因此我们怎么解决这种平均分配消息呢,也就是说谁消费能力高谁就消费消息的多,

做法如下所示:

 这个配置成1:也就是说你俩都别预取了,你俩谁消费完1个后(如果设置的是2那么就消费完2个后)再来我队列中拿消息把,这样消费者1的消费能力高那么消费者1肯定就会去队列中拿的消息数据多了。

 

三、发布订阅模型

        

        首先我们前面讲过的hello world模型和 Work  Queue 工作模型实现消息发送和消费的过程都是如下所示的:(都是消息提供者先把消息发送到queue队列当中,然后消息消费者获取消费queue队列中暂存的消息  【工作模型的话消费者是两个】

而订阅模型是什么呢:

        

总结:

也就是说订阅模型相比着前面两种模型来说:

        前面两种模型都是消息提供者先把消息发送到queue队列中,而订阅模型的话则是消息提供者先把消息发送给exchange交换机,然后exchange交换机通过路由的方式把消息路由发送给队列中。其他的过程都是一样的(也就是说只有消息提供者是把消息发送给exchange交换机然后路由给绑定的队列当中了,而消息消费者消费数据的时候还是直接从队列当中拿消息进行消费的)。

        

注意1:exchange交换机只负责路由消息提供者发送的消息到队列中,而不储存消息数据,如果路由失败的话,那么消息数据就直接丢失了。

注意2:exchange交换机有三个类型:Fanout(广播)、Direct(路由)、Topic(话题)

3.1、FanoutExchange(广播交换机)的使用

 总结一句话:

        也就是说我们知道了订阅模型的话是通过先把消息发送者发送的消息发送给交换机中,然后通过路由到队列当中的进行暂存数据的,如何路由到队列中:只有队列和交换机绑定了关系那么队列才能收到交换机中路由的消息数据(比如上面的queue1、queue2两个队列,只要这两个队列都和交换机绑定了关系了,那么这两个队列中就能收到暂存到消息发送者publisher发送的消息了,最终消息消费者就可以在queue1或者queue2队列中获取到消息发送者发送的消息了,也就是说在这两个队列中都可以获取到消息发送者发送的消息咯)

具体java代码演示如下所示:

第一步:

首先我们知道消息发送者是先把消息发送到交换机当中的,并且把消息数据路由到和这个交换机绑定了关系的队列当中的,因此我们第一步就是先在消息消费者中把交换机和队列创建声明出来,并且让这个交换机和队列绑定好关系

 

package cn.itcast.mq.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration  // 这个配置类注解一定要加上
public class FanoutConfig {
    /**
     *  : 也就是说声明创建一个itcast.fanout为名的fanout类型的交换机
     */
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("itcast.fanout"); // 这里面填写的就是创建的交换机的名字
    }

    /**
     *  : 也就是说声明创建一个fanout.queue1为名的队列
     */
    @Bean
    public Queue fanoutQueue1(){
        return new Queue("fanout.queue1");  // 这里面填写的就是创建的队列的名字
    }

    /**
     *  : 也就是说把fanout.queue1为名的队列和itcast.fanout为名的fanout类型的交换机绑定上关系。
     *  (绑定上关系的话,那么这个队列跟着交换机大哥混,等交换机大哥收到消息发送者发送的消息后,交换机大哥就会把这个消息路由给这个队列中进行暂存消息数据)
     */
    @Bean
    public Binding fanoutBinding1(Queue fanoutQueue1, FanoutExchange fanoutExchange){   // 注意:这里的参数一个是队列的方法名,一个是交换机的方法名
        return BindingBuilder    // 然后通过这个BindingBuilder对象就可以把上面的队列和交换机进行绑定了。
                .bind(fanoutQueue1)
                .to(fanoutExchange);
    }

    /**
     *  : 也就是说再声明创建一个fanout.queue2为名的队列
     */
    @Bean
    public Queue fanoutQueue2(){
        return new Queue("fanout.queue2");
    }

    /**
     *  : 也就是说把fanout.queue2为名的队列和itcast.fanout为名的fanout类型的交换机绑定上关系。
     *  (同理绑定上关系的话,那么这个队列跟着交换机大哥混,等交换机大哥收到消息发送者发送的消息后,交换机大哥就会把这个消息路由给这个队列中进行暂存消息数据)
     */
    @Bean
    public Binding fanoutBinding2(Queue fanoutQueue2, FanoutExchange fanoutExchange){
        return BindingBuilder
                .bind(fanoutQueue2)
                .to(fanoutExchange);
    }

}

通过上面写的这个配置类,那么我们就知道了现在交换机和两个队列都创建成功了,并且这两个队列和交换机也绑定上关系了。

        绑定上关系后:也就是说比如消息发送者发送了一个hello every one的消息,那么此时这个消息就会发送到交换机上了,然后因为这两个队列和交换机绑定了关系,因此这两个队列都能路由到交换机中的消息发送者发送的消息(也就是说此时这两个队列当中都暂存到了消息发送者发送的hello every one的消息数据了)。然后最终消息消费者就可以对任意一个队列中订阅获取队列中暂存的hello every one的消息数据咯。

然后我们启动消息消费者的启动类:

 就会发现,我们声明创建的itcast.fanout为名的交换机和fanout.queue1、fanout.queue2为名的两个队列在RabbitMQ上确实给我们创建成功了并且这个交换机确实也和这两个队列绑定好关系了。

 

 

第二步: 

        我们知道两个队列创建好之后,那么我们消息消费者就可以向队列中订阅消费队列中的暂存的消息数据了,我们这里假设有两个消费者A、B,A消息消费者消费queue1队列中的消息数据,B消息消费者消费queue2队列中的消息数据

会发现这两个消息消费者都能获取到消息发送者发送的消息数据,因为这两个队列都绑定了交换机,那么交换机路由时会把消息发送者的数据分别给这两个队列各发送一份

因此就在消息消费者中写代码逻辑: 

 注意:一定要先保证消费者是开启状态,然后再写消息发送者进行向队列中发送消息数据(等队列中有消息的时候,这里消费者就可以直接消费队列中的消息数据了)

第三步:

 我们知道交换机和队列还有消息消费者都准备好之后,那么我们就可以新建一个消息发送者向交换机当中发送消息数据了(然后路由给我们绑定关系的队列中),最后供消息消费者消费消息数据。

 因此我们就要在消息发送者模块中写逻辑了:

 @Autowired
    private RabbitTemplate rabbitTemplate;


    @Test
    public void testSendFanoutExchange() {
        // 交换机名称
        String exchangeName = "itcast.fanout";  // 向刚才我们创建的itcast.fanout为名的交换机中发送"hello, every one!"
                                                // 的消息数据
        // 消息
        String message = "hello, every one!";
        // 发送消息
        rabbitTemplate.convertAndSend(exchangeName, "", message);   // 向交换机中发送消息
    }

结论:

因此通过上面的三个步骤的操作,我们知道消费者是开启状态的,等着队列中有消息数据的时候就会消费队列中的消息数据,那么我们第三步通过向队列中发送消息后,那么交换机就会把这条消息发送者发送的消息路由给queue1和queue2绑定的两个队列当中了,那么我们消费者A和消费者B此时就获取消费到了队列中的消息发送者发送的消息数据了:

3.2、DirectExchange(路由交换机)模式

代码演示上面过程如下所示:

修改:上面利用 @RabbitListener 声明的是Bindingkey

 第一步:使用@RabbitListener注解声明交换机、队列、BindingKey

@Component
public class SpringRabbitListener {

    /**
     *  第一步:使用@RabbitListener注解声明一个名为itcast.direct1的direct路由模式的交换机、
     *         交换机有了之后,我们就可以使用@Queue注解声明创建队列了,我们这里就创建了一个direct.queue1为名的队列
     *         并且这个队列绑定key(也就是说BindingKey)绑定了red和blue两种。
     *
     *         也就是说等会只要交换机接收的消息对应的RoutingKey是blue或者red,那么这个交换机就会把这个消息路由给这个direct.queue1为名的队列
     *         (因为这个队列BindingKey也为blue和red,因此只要和交换机接收的消息对应的RoutingKey一致的话,那么该队列就能路由到消息)
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue1"),
            exchange = @Exchange(name = "itcast.direct1", type = ExchangeTypes.DIRECT), // type的作用就是声明这个交换机是direct路由的模式
            key = {"red", "blue"}      // 该direct.queue1队列的BindingKey为red和blue        // 还可以用 type = ”direct“ 和上面用枚举的效果是一样的
    ))

    // 这个消息消费者直接就对这个direct.queue1为名的队列中暂存的消息进行消费 (可以理解为上面的direct.queue1队列注解在这个消费者方法上
    // 所以就对这个direct.queue1队列进行消费)
    public void listenDirectQueue1(String msg){ // 注意:这个消费接收到的消息类型要保证和队列中的消息数据类型一致(String类型)
        System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "direct.queue2"), // 同理这里又声明创建了一个direct.queue2为名的队列
            exchange = @Exchange(name = "itcast.direct1", type = ExchangeTypes.DIRECT), // 绑定声明的交换机还是itcast.direct1为名的交换机
            key = {"red", "yellow"} // 并且为这个direct.queue2为名的队列设置的BindingKey为red和yellow
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到direct.queue2的消息:【" + msg + "】");
    }
}

通过使用这个@RabbitListener注解声明,我们知道了我们声明创建成功了一个"itcast.direct1"为名的交换机了,并且两个队列direct.queue1、direct.queue2也都声明创建成功了,而且这个direct.queue1队列我们绑定的BindingKey为blue和red,direct.queue2队列

我们绑定的BindingKey为red和yellow:

 

我们刚才上面分析知道了,只有当交换机接收到的消息对应的RoutingKey和队列绑定的BindingKey一致的时候,那么该队列才能接收到交换机路由的消息数据 

演示1:

我们现在假定消息发送者向 itcast.direct1"为名的交换机中发送的消息对应的RoutingKey为blue,我们知道两个队列中只有direct.queue1队列我们绑定的BindingKey有这个blue,因此这个队列和消息的RoutingKey是一致的,所以这个队列能接收到交换机路由的这个消息发送者发送的消息数据。(而direct.queue2这个队列中绑定的BindingKey没有和这个消息的RoutingKey一致的key数据,因此这个direct.queue2队列就接收不到交换机路由的消息数据了。

也就是说:交换机说了你们队列谁和我的规则一样我就把消息路由给谁,不一样你还想要消息数据?

代码演示如下所示:

会发现还真的只有direct.queue1队列接收到了交换机路由的消息发送者发送的消息数据了:

 演示2:

我们现在假定消息发送者向 itcast.direct1"为名的交换机中发送的消息对应的RoutingKey为red,我们知道两个队列中绑定的BindingKey都有这个red,因此这两个队列和消息的RoutingKey都是一致的,所以这两个队列都能接收到交换机路由的这个消息发送者发送的消息数据。

总结:(面试用)

3.3、TopicExchange(发布订阅)交换机模型

 总结:TopicExchange交换机和上面的DirectExchange交换机的区别两点:

        1、TopicExchange交换机消息对应的Routingkey必须是多个单词列表,并且以.分割

        2、TopicExchange交换机中Bindingkey可以使用通配符的方式

除了这两点外其他的代码逻辑都是一样的。

 代码演示如下所示:

 

@Component
public class SpringRabbitListener {

    /**
     *  第一步:使用@RabbitListener注解声明一个名为itcast.topic1的topic模式的交换机、
     *         交换机有了之后,我们就可以使用@Queue注解声明创建队列了,我们这里就创建了一个topic.queue1为名的队列
     *         并且这个队列绑定key(也就是说BindingKey)绑定了red和blue两种。
     */
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue1"),
            exchange = @Exchange(name = "itcast.topic1", type = ExchangeTypes.TOPIC), // type的作用就是声明这个交换机是topic1模式
            key = "china.#"      // 该topic.queue1队列的BindingKey为"china.#"            // 还可以用 type = ”TOPIC“ 和上面用枚举的效果是一样的
    ))

    // 这个消息消费者直接就对这个direct.queue1为名的队列中暂存的消息进行消费 (可以理解为上面的direct.queue1队列注解在这个消费者方法上
    // 所以就对这个direct.queue1队列进行消费)
    public void listenDirectQueue1(String msg){ // 注意:这个消费接收到的消息类型要保证和队列中的消息数据类型一致(String类型)
        System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
    }


    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "topic.queue2"), // 同理这里又声明创建了一个direct.queue2为名的队列
            exchange = @Exchange(name = "itcast.topic1", type = ExchangeTypes.TOPIC), // 绑定声明的交换机还是itcast.topic1为名的交换机
            key = "#.news"   // 并且为这个topic.queue2为名的队列设置的BindingKey为 "#.news"
    ))
    public void listenDirectQueue2(String msg){
        System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
    }
}

 

 注:queue2队列对应的bindingkey为 #.news的格式,因此消息发送者发送的消息RoutingKey格式为 china.weather格式的时候,这个queue2对应的bindingkey和RoutingKey是不会相同的,只有queue1队列对应的bindingkey为 china.#的格式才和这个消息RoutingKey相同一致,因此这条消息交换机就只会路由到queue1队列当中,不会路由到queue2队列当中

四、SpringAMQP消息转换器 ****

我们就拿简单的hello world 模型进行演示问题所在:

 我们知道hello world模型,我们消息发送者只需要直接把消息发送给队列即可,然后消息消费者直接在队列中订阅获取数据就可以了,因此相比着需要和那几个绑定交换机的简单多了,因此我们就拿这个hello world模型进行代码演示问题把:

问题演示如下所示:

        问题就是我们上面五种模型中,我们都是往队列中发送的是String字符串类型的消息数据(如下面的hello spring amqp!),

这里仅以hello world最简单的模型为例演示:

现在呢就是说我们能不能向队列中发送消息的时候以对象的形式发送呢,比如说以map集合对象的形式往队列中发送消息 :

 

分析原因:

原因就是我们RabbitMQ只支持字节的形式消息数据,而我们刚才代码中向队列中发送消息时spring是支持向队列中发送对象形式的消息的,而RabbitMQ只支持字节的形式的数据因此我们对象形式的数据就被序列化成字节形式的数据了,因此在队列中查看到的数据就是字节的形式了。(也就是说对象序列化成字节了)

因此解决这种我们发送的对象消息被序列化的问题,我们就需要使用消息转换器了: 

 

 

注意:虽然现在在队列中展示的是对象形式的消息数据了,但是其实还是字节的形式数据

因此,我们消息消费者如果想要接收获取这个队列中上面这个对象形式的消息的话(实际上还是字节的形式数据),还需要用到消息转换器:

消息转换器: 把队列中字节形式消息 ---- 序列化-----成对象的形式供消息消费者使用

 

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

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

相关文章

SpringCloudAlibaba【六】微服务架构下的秒杀案例

背景 分布式微服务中的秒杀是怎么实现的呢&#xff1f;接着看下去吧 我们实现一个秒杀微服务&#xff0c;流程逻辑如下 项目搭建 MySQL create database if not exists demo;use demo;drop table if exists skill_goods;create table if not exists skill_goods (id bigint…

1024程序员节|【MySQL从入门到精通】【高级篇】(二十七)外连接和内连接如何进行查询优化呢?join的原理了解一波

您好&#xff0c;我是码农飞哥&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精通 ❤️ 2.网上优质的Python题库很少…

[面试宝典] Linux常见命令及面试题

前言&#xff1a; &#x1f604;作者简介&#xff1a;小曾同学.com,小伙伴们也可以叫我小曾&#xff0c;一个致力于测试开发的博主⛽️ 如果文章知识点有错误的地方&#xff0c;还请大家指正&#xff0c;让我们一起学习&#xff0c;一起进步。&#x1f60a; 座右铭&#xff1a;…

C语言 - 你一定能看懂的扫雷万字详解(加入了递归展开和手动标雷的功能)

C语言之扫雷详解&#xff08;包含递归展开和手动标雷功能&#xff0c;非常强大&#xff01;&#xff09; 文章目录一.写在前面二.效果展示三.整体逻辑四.详解1.进入主函数&#xff0c;打印菜单&#xff0c;玩家作出选择2.定义棋盘的数组并进行赋值3.棋盘的展示4.随机布雷5.开始…

前端开发入门--html--Flask

快速开发网站 pip install flaskfrom flask import Flaskapp Flask(__name__)# 创建了网址 /show/info 和 函数index 的对应关系 # 以后用户在浏览器上访问 /show/info&#xff0c;网站自动执行 index app.route("/show/info") def index():return "中国联通&…

TPH-YOLOv5 | 基于Transformer的YOLOv5小目标检测器 | 四头加注意力

论文地址&#xff1a;https://arxiv.org/pdf/2108.11539.pdf 项目地址&#xff1a;https://github.com/cv516Buaa/tph-yolov5 在无人机捕获的场景中进行对象检测是最近的一项热门任务。由于无人机总是在不同的高度航行&#xff0c;物体尺度变化剧烈&#xff0c;给网络优化带来…

NMEA协议解析

文章目录一、NMEA0183协议1、NMEA基本框架2、常用语句1&#xff09;GNGGA2&#xff09;GNGLL3&#xff09;GNGSA4&#xff09;GPGSV5&#xff09;GNRMC6&#xff09;GNVTG7&#xff09;GNZDA8&#xff09;PAIRCLK等二、异或校验和代码1、网址在线计算BCC2、BCC校验和代码一、NM…

Java语言中的异常处理

异常处理 在java语言中&#xff0c;很机智的将异常作为对象来处理&#xff0c;而且定义一个基类java.lang.Throwable作为所有异常类的父类。在这许多类中一般分为两大类&#xff1a; 错误类(Error)和异常类&#xff08;Expception&#xff09;。 如图&#xff1a; 注&#xf…

iNOF在现实网络中的运用,以带反射器的iONF为例

定义 iNOF&#xff08;Intelligent Lossless NVMe Over Fabric&#xff0c;智能无损存储网络&#xff09;是指通过对接入主机的快速管控&#xff0c;将智能无损网络应用到存储系统&#xff0c;实现计算和存储网络融合的技术。 目的 网络转发设备用于传输流量&#xff0c;不同类…

竞争不是内卷,用头脑学习,而非时间

文章目录 用头脑学习&#xff0c;而非时间 前言 一、自由竞争不是内卷 二、内卷都在哪些行业 三、高效学习来大数据梦想联盟 用头脑学习&#xff0c;而非时间 前言 大多数人不懂&#xff0c;不会&#xff0c;不做&#xff0c;才是你的机会&#xff0c;你得行动&#xff…

【Queue】- 从源码分析ArrayDeque及其常用方法

文章目录概述ArrayDeque基础知识ArrayDeque内部结构ArrayDeque的构造方法ArrayDeque的扩容操作ArrayDeque常用方法将ArrayDeque作为双端队列使用时public void addFirst(E e)public void addLast(E e)public boolean offerFirst(E e)public boolean offerLast(E e)public E pol…

动态SLAM论文归纳

持续更新&#xff0c;持续更新 2022 Multi-modal Semantic SLAM for Complex Dynamic Environments 作者&#xff1a;Han Wang, Jing Ying Ko and Lihua Xie, Fellowcode&#xff1a;https://github.com/wh200720041/MMS_SLAM视频&#xff1a;https://www.youtube.com/watch…

web自动化测试——入门篇01

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;【Austin_zhai】 &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xff0c;分享行业相关最新信息。…

并发编程中的原子性,可见性,有序性问题

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章是关于并发编程中出现的原子性&#xff0c;可见性&#xff0c;有序性问题。 本篇文章记录的基础知识&#xff0c;适合在学Java的小白&#xff0c;也适合复习中&am…

PyTorch(三)TensorBoard 与 Transforms

文章目录Log一、TensorBoard1. TensorBoard 的安装2. SummaryWriter 的使用① add_scalar() 的使用a. 参数说明b. 函数使用c. 使用 Tensorboard② add_image() 的使用a. 参数说明b. 使用 numpy.array() 对 PIL 图片进行转换c. 使用函数d. 改变 global_step二、Transforms1. Tra…

数据结构 | 时间复杂度与空间复杂度

… &#x1f333;&#x1f332;&#x1f331;本文已收录至&#xff1a;数据结构 | C语言 更多知识尽在此专栏中&#xff01; &#x1f389;&#x1f389;&#x1f389;欢迎点赞、收藏、关注 &#x1f389;&#x1f389;&#x1f389;文章目录&#x1f333;前言&#x1f333;正…

【C++初阶】类和对象(二)

大家好我是沐曦希&#x1f495; 类和对象1.类的6个默认成员函数2.构造函数2.1 概念2.2 特性3.析构函数3.1 概念3.2 特性4.拷贝构造函数4.1 概念4.2 特征1.类的6个默认成员函数 空类&#xff1a;类中一个成员都没有 可是空类真的什么都没有吗&#xff1f; 并不是&#xff0c;任…

STM32关于UART的接收方式

STM32的 UART 一般分为定长接收和不定长接收 定长接收&#xff1a; HAL_UART_Receive():只能接收固定长度的数据&#xff0c;如果超过固定长度的数据只能接收对应长度&#xff0c;如果小于固定长度则不会接收 HAL_UART_Receive_IT():中断方式接收&#xff0c;每接收一个字节…

CSS 2 CSS 选择器 - 5 2.8 伪选择器 2.8.1 伪类选择器【根据特定状态选取元素】

CSS 文章目录CSS2 CSS 选择器 - 52.8 伪选择器2.8.1 伪类选择器【根据特定状态选取元素】2 CSS 选择器 - 5 2.8 伪选择器 2.8.1 伪类选择器【根据特定状态选取元素】 【什么是伪类】 伪类用于定义元素的特殊状态。 例如&#xff0c;它可以用于&#xff1a; 设置鼠标悬停在…

如何删除ZIP压缩包的密码?

ZIP是比较常用的压缩文件格式&#xff0c;有时候因为工作需要很多人还会给压缩包设置打开密码。那如果后续不需要密码保护了要如何删除密码呢&#xff1f;密码忘记了还能删除吗&#xff1f; 首先来说说第一种情况&#xff0c;也就是知道密码但后续不需要密码保护&#xff0c;只…