04- 基于SpringAMQP封装RabbitMQ,消息队列的Work模型和发布订阅模型

SpringAMQP

概述

使用RabbitMQ原生API在代码中设置连接MQ的参数比较繁琐,我们更希望把连接参数写在yml文件中来简化开发

SpringAMQP是基于AMQP协议定义的一套API规范,将RabbitMQ封装成一套模板用来发送和接收消息

  • AMQP(Advanced Message Queuing Portocol)是用于在应用程序之间传递业务消息的开放标准, 该协议与语言和平台无关更符合微服务中独立性的要求
  • SpringAMQP由两部分组成: spring-AMQP是基础抽象,spring-Rabbit是底层的默认实现,并且利用SpringBoot对其实现了自动装配

SpringAMQP提供的三个功能

  • 自动声明队列、交换机及其绑定关系
  • 基于注解的监听模式(监听器容器),用于异步异步处理入站消息
  • 封装了RabbitTemplate工具,用于发送消息(之前在Redis中我们也接触过RedisTemplate)
方法名功能
convertAndSend(队列名称, Object类型的消息对象)发送消息到队列
convertAndSend(交换机名称, 路由key, Object类型的消息对象)发送消息到交换机,交换机再根据路由规则发送到对应的队列
注解名功能(声明交换机和队列时也可以基于配置类的方式注入到容器中)
@RabbitListener注解声明要监听/绑定的队列
@Exchange声明交换机
@Queue声明队列
@QueueBinding声明交换机和队列的绑定关系

Work模型

Wrok模型的使用: 多个消费者绑定到一个队列,同一条消息只会被一个消费者处理, 可以设置prefetch来控制消费者预取的消息数量

Basic Queue

第一步: 在父工程mq-demo中引入AMQP依赖,这样子模块也继承了该依赖就可以基于RabbitTemplate模板实现消息的发送和接收

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

第二步:在publisher服务的application.yml中配置MQ地址,Spring已经帮我们跟MQ建立了连接,剩下只需要指定发送什么消息到哪个队列

spring:rabbitmq:host: 192.168.88.128 # 主机名port: 5672 #端口username: root # 用户名password: 123456 # 密码virtual-host: / # 虚拟主机

第三步: 在publisher服务中编写测试类SpringAmqpTest利用RabbitTemplate发送消息到指定队列(如果没有创建simple.queue可以在RabbitMQ管理平台创建)

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Autowiredprivate RabbitTemplate rabbitTemplate;@Testpublic void testSimpleQueue() {String queueName = "simple.queue";String message = "Hello, SpringAMQP! ";rabbitTemplate.convertAndSend(queueName, message);}
}

第四步:在consumer服务的application.yml中同样配置MQ地址,这样Spring已经帮我们跟MQ建立了连接,剩下只需要关心要监听哪个队列以及监听到要干什么事儿

spring:rabbitmq:host: 192.168.88.128 # 主机名port: 5672 #端口username: root # 用户名password: 123456 # 密码virtual-host: / # 虚拟主机

第五步: 在consumer服务新建一个类用于编写消费逻辑,使用@Component注解将该类声明为一个Bean,使用@RabbitListener注解声明要监听/绑定的队列

@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) {// msg参数就是接收到的消息,Spring已经帮我们二进制数据转成了字符串System.out.println("Spring 消费者接收到消息:【" + msg + "】");}
}

第六步: 启动Consumer服务查看控制台接收到的消息,如果多次使用publisher服务发送消息consumer服务也会接收多次消息

  • 消息一旦被消费就会从队列删除,RabbitMQ没有消息回溯功能
Spring 消费者接收到消息:【Hello, SpringAMQP! 】

Wrok Queue(Task queues)

Work Queue也被称为任务模型,简单来说就是让多个消费者绑定到一个队列让它们共同处理队列中的信息提高消息的处理速度(同一条消息只会被一个消费者处理)

  • 如果消息处理比较耗时那么生产消息的速度会远远大于消息的消费速度,就会导致消息堆积的越来越多无法及时处理

在这里插入图片描述

第一步: 在publisher服务中的SpringAmqpTest类测试方法中循环发送消息模拟大量消息堆积的场景

@Test
public void testWorkQueue() throws InterruptedException {String queueName = "simple.queue";String message = "Hello, SpringAMQP! __ ";for (int i = 1; i <= 50; i++) {// 循环发送50条消息,带上消息编号rabbitTemplate.convertAndSend(queueName, message + i);// 休眠20ms,模拟在1s内发送完Thread.sleep(20);}
}

第二步: 在consumer服务的SpringRabbitListener类中添加两个方法模拟多个消费者绑定同一个队列(一个方法对应一个消费者)

@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage1(String msg) throws InterruptedException {System.out.println("消费者1 接收到消息:【" + msg + "】" + LocalDateTime.now());// 休眠20ms,1s大致能处理50条消息Thread.sleep(20);
}@RabbitListener(queues = "simple.queue")
public void listenWorkQueueMessage2(String msg) throws InterruptedException {System.err.println("消费者2........接收到消息:【" + msg + "】" + LocalDateTime.now());// 休眠200ms,1s大概能处理5条消息Thread.sleep(200);
}

第三步: 执行publisher服务中刚编写的测试方法发送50条消息,启动consumer服务查看控制台输出

  • 消费者1很快就完成了自己的25条消息,消费者2却在缓慢的处理自己的25条消息,即当前的处理方式是平均分配给每个消费者
消费者2........接收到消息:【Hello, SpringAMQP! __ 1】2022-12-23T13:16:41.407
消费者1 接收到消息:【Hello, SpringAMQP! __ 2】2022-12-23T13:16:41.414
消费者1 接收到消息:【Hello, SpringAMQP! __ 4】2022-12-23T13:16:41.461
消费者1 接收到消息:【Hello, SpringAMQP! __ 6】2022-12-23T13:16:41.502
消费者1 接收到消息:【Hello, SpringAMQP! __ 8】2022-12-23T13:16:41.549
消费者1 接收到消息:【Hello, SpringAMQP! __ 10】2022-12-23T13:16:41.592
消费者2........接收到消息:【Hello, SpringAMQP! __ 3】2022-12-23T13:16:41.609
消费者1 接收到消息:【Hello, SpringAMQP! __ 12】2022-12-23T13:16:41.635
消费者1 接收到消息:【Hello, SpringAMQP! __ 14】2022-12-23T13:16:41.680
消费者1 接收到消息:【Hello, SpringAMQP! __ 16】2022-12-23T13:16:41.722
消费者1 接收到消息:【Hello, SpringAMQP! __ 18】2022-12-23T13:16:41.767
....
消费者2........接收到消息:【Hello, SpringAMQP! __ 35】2022-12-23T13:16:44.837
消费者2........接收到消息:【Hello, SpringAMQP! __ 37】2022-12-23T13:16:45.040
消费者2........接收到消息:【Hello, SpringAMQP! __ 39】2022-12-23T13:16:45.240
消费者2........接收到消息:【Hello, SpringAMQP! __ 41】2022-12-23T13:16:45.444
消费者2........接收到消息:【Hello, SpringAMQP! __ 43】2022-12-23T13:16:45.646
消费者2........接收到消息:【Hello, SpringAMQP! __ 45】2022-12-23T13:16:45.846
消费者2........接收到消息:【Hello, SpringAMQP! __ 47】2022-12-23T13:16:46.048
消费者2........接收到消息:【Hello, SpringAMQP! __ 49】2022-12-23T13:16:46.250

我们希望按照服务器的处理能力来处理消息,避免出现消息积压的风险,在consumer服务中的application.yml文件添加prefetch配置控制预取消息的上限

spring:rabbitmq:listener:simple:prefetch: 1 # 每次只能获取一条消息,处理完成才能获取下一个消息

发布订阅模型

在订阅模型中多了一个exchange角色,发送消息的流程也发生了变化

  • Publisher(生产者): 消息不再发送到队列中而是发送给exchange(交换机)
  • Exchange(交换机): 一方面负责接收生产者发送的消息,另一方面负责将消息按照规则路由到与之绑定的队列(如递交给某个特定队列、递交给所有队列)
  • Consumer(消费者): 订阅队列的消息
  • Queue(消息队列): 接收消息并缓存消息

交换机如何处理接收到的消息取决于Exchange的类型

  • Fanout(广播): 将消息交给所有绑定到交换机的队列
  • Direct(定向): 把消息交给符合指定routing key的队列
  • Topic(通配符): 把消息交给符合routing pattern(路由模式)的队列
  • 交换机只负责转发消息不具备存储消息的能力,因此如果没有任何队列与Exchange绑定或者没有符合路由规则的队列,那么消息会丢失

Spring提供了一个接口Exchange来表示所有不同类型的交换机(路由规则不同),Queue和Binding也是Springframework提供的类
在这里插入图片描述

Fanout(广播)

在广播模式下消息发送的流程

  • 广播模式下可以声明多个队列,但每个队列都要绑定一个Exchange(交换机)
  • 生产者发送的消息只能发送到交换机,由交换机决定要发给哪个队列
  • FanoutExchange类型的交换机会把消息发送给每一个跟其绑定的队列,然后订阅队列的消费者都能拿到同一条消息

在这里插入图片描述

第一步: 在consumer服务创建一个配置类config/FanoutConfig用来声明队列(Queue)和交换机(FanoutExchange)以及队列和交换机的绑定关系(Binding)

@Configuration
public class FanoutConfig {/*** 声明交换机* @return Fanout类型交换机*/@Beanpublic FanoutExchange fanoutExchange() {return new FanoutExchange("itcast.fanout");}/*** 第1个队列,方法的名称就是bean的名称*/@Beanpublic Queue fanoutQueue1() {return new Queue("fanout.queue1");}/*** 绑定第1个队列和交换机,根据方法形参的名称自动注入容器中对应的bean(队列和交换机)*/@Beanpublic Binding bindingQueue1(Queue fanoutQueue1, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);}/*** 第2个队列*/@BeanQueue fanoutQueue2() {return new Queue("fanout.queue2");}/*** 绑定第2个队列和交换机,根据方法形参的名称中自动注入容器中对应的bean(队列和交换机)*/@Beanpublic Binding bindingQueue2(Queue fanoutQueue2, FanoutExchange fanoutExchange) {return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);}
}

第二步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送一次消息

@Test
public void testFanoutExchange() {String exchangeName = "itcast.fanout";String message = "Hello Everyone~";// 交换机名称,路由key,消息rabbitTemplate.convertAndSend(exchangeName, "", message);
}

第三步: 在consumer服务的SpringRabbitListener中添加两个方法作为两个消费者分别绑定fanout.queue1fanout.queue2接收消息

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {System.out.println("消费者1收到广播消息:【" + msg + "】");
}@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {System.out.println("消费者2收到广播消息:【" + msg + "】");
}

第四步: 重启consumer服务然后运行publisher中新编写的测试方法查看控制台输出

消费者1收到广播消息:【Hello Everyone~】
消费者2收到广播消息:【Hello Everyone~】

Direct(基于注解方式声明)

在Fanout广播模式中一条消息会发给所有与交换机绑定队列,但是在某些场景下我们更希望不同的消息发送给不同的队列,这时就要用到Direct类型的Exchange

Direct类型的Exchange不再把接收的消息交给每一个与其绑定的队列,只有当队列的BindingKey消息的RoutingKey完全一致时该队列才会收到消息

  • 每一个队列与虚拟机绑定时需要指定一个BindingKey(路由key)
  • 发布者向Exchange发送消息时也必须指定消息的RoutingKey(路由规则)
  • Exchange将消息路由到BindingKey和消息的RoutingKey一致的队列

在这里插入图片描述

Direct交换机与Fanout交换机的差异

  • Fanout交换机会将消息路由给每一个与之绑定的队列
  • Direct交换机根据RoutingKey判断路由给哪个队列,如果多个队列具有相同的RoutingKey,则与Fanout功能类似,也是把消息路由给每一个匹配的队列

第一步: 在consumer服务SpringRabbitListener类中添加两个消费者监听direct.queue1和direct.queue2同时基于RabbitListener注解来声明队列和交换机

  • 由于基于Bean的方式声明队列与交换机比较麻烦所以Spring还提供了基于注解的方式来声明Exchange(@Exchange),Queue(@Queue),Binding(@QueueBinding)
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),// 默认type = "direct"key = {"red", "blue"}// key就是队列和交换机的BindingKey
))
public void listenDirectQueue1(String msg) {System.out.println("消费者收到direct.queue1的消息:【" + msg + "】");
}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),exchange = @Exchange(name = "directExchange", type = ExchangeTypes.DIRECT),key = {"red", "yellow"}
))
public void listenDirectQueue2(String msg) {System.out.println("消费者收到direct.queue2的消息:【" + msg + "】");
}

第二步: 在publisher中编写三个测试方法向directExchange发送三条消息并指定消息的RoutingKey

@Test
public void testDirectExchange() {String exchangeName = "directExchange";String message = "hello,blue";rabbitTemplate.convertAndSend(exchangeName, "blue", message);
}@Test
public void testDirectExchange() {String exchangeName = "directExchange";String message = "hello,yellow";rabbitTemplate.convertAndSend(exchangeName, "yellow", message);
}@Test
public void testDirectExchange() {String exchangeName = "directExchange";String message = "hello,red";rabbitTemplate.convertAndSend(exchangeName, "red", message);
}

第三步: 重启consumer服务运行以上三个测试方法查看控制台输出

消费者收到direct.queue1的消息:hello,blue
消费者收到direct.queue2的消息:hello,yellow
消费者收到direct.queue1的消息:hello,red
消费者收到direct.queue2的消息:hello,red

Topic(使用通配符)

Topic类型和Direct类型的Exchange都可以根据RoutingKey把消息路由到不同的队列,只不过Topic类型的Exchange与队列绑定时指定的BindingKey可以使用通配符

  • #: 匹配一个或多个词,如item.#能够匹配item.kyle.violet或者item.kyle
  • *: 仅匹配一个词,如item.*只能匹配item.kyle或者item.violet

Direct交换机与Topic交换机的差异

  • Topic交换机接收的消息Routing Key必须是多个单词,多个单词间以.分割
  • Topic交换机与队列绑定时的Binding key可以指定通配符,其中#表示0个或多个单词,*仅表示1个单词

在这里插入图片描述

第一步: 在publisher服务的SpringAmqpTest类中添加测试方法向交换机发送不同RoutingKey的消息

@Test
public void testTopicExchange() {String exchangeName = "topic";String message = "如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?";rabbitTemplate.convertAndSend(exchangeName, "china.news", message);
}@Test
public void testTopicExchange() {String exchangeName = "topic";String message = "今 天 也 是 个 emo 的 好 天 气";rabbitTemplate.convertAndSend(exchangeName, "china.weather", message);
}@Test
public void testTopicExchange() {String exchangeName = "topic";String message = "自由美利坚,枪击每一天";rabbitTemplate.convertAndSend(exchangeName, "us.news", message);
}

第二步: 在consumer服务的SpringRabbitListener类中消费方法监听topic.queue1和topic.queue2, 利用@RabbitListener相关的注解声明Exchange,Queue,BindingKey

@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),key = "china.#"
))
public void listenTopicQueue1(String msg) {System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"),exchange = @Exchange(name = "topic", type = ExchangeTypes.TOPIC),key = "#.news"
))
public void listenTopicQueue2(String msg) {System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
}
消费者接收到topic.queue2的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【如何看待马化腾称「短视频会侵蚀游戏时间」,「腾讯游戏要聚焦精品」?】
消费者接收到topic.queue1的消息:【今 天 也 是 个 emo 的 好 天 气】
消费者接收到topic.queue2的消息:【自由美利坚,枪击每一天】

消息转换器

Spring的convertAndSend方法接收消息的类型是Object也就是说它可以把任意对象类型的消息序列化为字节发送给MQ(默认采用JDK的序列化方式)

第一步: 在config.FanoutConfig中声明一个队列

@Bean
public Queue objectQueue(){return new Queue("object.queue");
}

第二步: 在测试方法中发送一个Map类型的对象消息到MQ

@Test
public void testSimpleQueue() {String queueName = "simple.queue";HashMap<String, Object> hashMap = new HashMap<>();hashMap.put("名称", "艾尔登法环");hashMap.put("价格", 299);rabbitTemplate.convertAndSend(queueName, hashMap);
}

JDK序列化的方式存在数据体积过大,有安全漏洞,可读性差的问题,我们更希望消息的体积更小可读性更高,因此可以使用JSON方式来做序列化和反序列化

在这里插入图片描述

第一步: 在publisher和consumer两个服务中引入依赖(或者直接在父工程mq-demo中引入依赖)

<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.9.10</version>
</dependency>

第二步: 在publisher和consumer的启动类中都添加jsonMessageConverter并注册到容器中

@Bean
publis MessageConverter jsonMessageConverter() {return new Jackson2JsonMessageConverter();
}

第三步: 再次执行测试方法发送一个Map类型的对象消息到MQ,修改consumer服务的SpringRabbitListener添加方法并重启服务

  • consumerpublisher的序列化器需保持一致,同时consumer中接收数据的类型也需要和发送数据的类型保持一致,如HashMap<String, Object>
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueueMessage(HashMap<String, Object> msg) throws InterruptedException {System.out.println("消费者接收到消息:【" + msg + "】");
}

在这里插入图片描述

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

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

相关文章

[CISCN2019 华东南赛区]Web11

模块注入题&#xff0c;这类题一般拥有固定的payload。 界面大概就是这么个样子 返回了IP地址&#xff0c;提示getip&#xff0c;xff等。 这是smarty模板。很明显了&#xff0c;这个模板存在xff处的命令执行。抓取数据包并添加字段 X-Forwarded-For:{{system(ls)}} cat /fla…

【Spring Boot 3】【Camel 4】动态路由

【Spring Boot 3】【Camel 4】动态路由 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总是要花费…

密码学——MAC

消息认证码 在信息发送和接收过程中,若攻击者能够得到信息,进行篡改,就能达到欺骗,诈骗,冒名顶替的作用。为了防止冒名诈骗,一个对策就是使用消息认证码——MAC: Message Authentication Code。 消息认证码,即确定消息真实性的认证程序。发件人将想要发送的信息和从哪个…

LeetCode18.四数之和

LeetCode19 四数之和 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] &#xff08;若两个四元组元素一一对应&#xff0c;则认为两个四元组重复&#xff09;&am…

【视觉三维重建】【论文笔记】Deblurring 3D Gaussian Splatting

去模糊的3D高斯泼溅&#xff0c;看Demo比3D高斯更加精细&#xff0c;对场景物体细节的还原度更高&#xff0c;[官网]&#xff08;https://benhenryl.github.io/Deblurring-3D-Gaussian-Splatting/&#xff09; 背景技术 Volumetric rendering-based nerual fields&#xff1a…

linux sshd_config配置说明

[root01 ssh]# cat sshd_config #######################SSH Base Config################## #######通过OpenSSH工具入xshell连接默认端口 可以改成其他默认是22 PAM 认证过程 1&#xff09;使用者执行/usr/bin/passwd程序&#xff0c;并输入密码。 2&#xff09;passwd开…

有效的正方形(LeetCode 593)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路边长验证法等腰直角三角形验证法正方形定义 参考文献 1.问题描述 给定 2D 空间中四个点的坐标 p1, p2, p3 和 p4&#xff0c;如果这四个点构成一个正方形&#xff0c;则返回 true 。 点的坐标 pi 表示为 [xi, yi] 。 输入没…

【开源-土拨鼠充电系统】鸿蒙 HarmonyOS 4.0+微信小程序+云平台

本人自己开发的开源项目&#xff1a;土拨鼠充电系统 ✍GitHub开源项目地址&#x1f449;&#xff1a;https://github.com/cheinlu/groundhog-charging-system ✍Gitee开源项目地址&#x1f449;&#xff1a;https://gitee.com/cheinlu/groundhog-charging-system ✨踩坑不易&am…

192基于matlab的雷达信号进行RD图的仿真

基于matlab的雷达信号进行RD图的仿真&#xff0c;在距离进行匹配滤波&#xff0c;具体方法是与回波信号的FFT与参考信号对称共轭的FFT相乘&#xff0c;再IFFT。在多普勒维通过多普勒滤波器组进行滤波&#xff0c;相当于进行FFT。程序已调通&#xff0c;可直接运行。 192 matlab…

牛客小白月赛60-C-小竹关禁闭

很经典的dp问题 对于求解动态规划类的问题,关键就是阶段,状态,决策,状态转移方程 本题的核心点就是,怎么找到上一个状态 dp[i] max(dp[i -1],dp[i - k - 1] a[i]) 当前点不选,它的上一个状态就是i - 1 当前点如果选,它的上一个状态是i - k - 1 关键就是它对应的上一个状…

Elasticsearch:使用标记修剪提高文本扩展性能

作者&#xff1a;来自 Elastic Kathleen DeRusso 本博客讨论了 ELSER 性能的令人兴奋的新增强功能&#xff0c;该增强功能即将在 Elasticsearch 的下一版本中推出&#xff01; 标记&#xff08;token&#xff09;修剪背后的策略 我们已经详细讨论了 Elasticsearch 中的词汇和…

C#十大排序总结

一、冒泡排序 传送门 一、C#冒泡排序算法-CSDN博客 未完待续。。。

CS架构和BS架构

在软件开发领域&#xff0c;CS架构和BS架构是两种常见的架构模式&#xff0c;用于描述客户端和服务器之间的交互方式。这两种架构模式分别代表Client/Server架构和Browser/Server架构。 CS架构&#xff08;Client/Server架构&#xff09;&#xff1a; 客户端&#xff08;Client…

测试用例执行计划(100%用例) C卷(JavaPythonC++Node.jsC语言)

某个产品当前迭代周期内有N个特性({F1,F2,., })需要进行覆盖测试,每个特性都被评估了对应的优先级,特性使用其ID作为下标进行标识。 设计了M个测试用例({T1,T2.……,Tm}),每个用例对应了一个覆盖特性的集合,测试用例使用其ID作为下标进行标识,测试用例的优先级定义为其覆盖…

KY211 特殊排序

描述&#xff1a; 输入一系列整数&#xff0c;将其中最大的数挑出(如果有多个&#xff0c;则挑出一个即可)&#xff0c;并将剩下的数进行排序&#xff0c;如果无剩余的数&#xff0c;则输出-1。 输入描述&#xff1a; 输入第一行包括1个整数N&#xff0c;1<N<1000&#x…

公式排序算法实际运用

试想下这个么个场景&#xff1a;用户可以自己配置多个公式&#xff0c;公式与公式之间又有依赖关系。比如ABC &#xff0c;BCD。需要做个算法来排序这些公式。实际我们可以分为两个步骤来看这个问题。 1&#xff0c;配置的公式之间不能死循环依赖。比如ABC ,BAC。这种A依赖…

Landsat、哨兵等免费数据下载地址汇总

我们科研和一些工程化应用中&#xff0c;经常会用到免费的Landsat、哨兵1/2/3等数据。下面介绍常用的下载网址&#xff1a; 1.哨兵系列数据 哨兵系列数据在https://scihub.copernicus.eu/dhus 上简单注册一个用户就可以下载&#xff0c;就是速度慢点&#xff0c;还限制一个用…

如何查看并详细了解一个R包

查看并拆解一个R包&#xff0c;如ggplot2&#xff0c;是一种深入了解其功能和内部工作原理的好方法。这个过程可以帮助你更好地理解包的结构&#xff0c;使用方式&#xff0c;以及如何扩展或修改其功能以满足你的需求。以下是一些基本步骤和技巧&#xff0c;以ggplot2为例进行解…

vue2——new Vue({router,store, render: h => h(App) }).$mount(‘#app‘);分析

router render: h > h(App) 是下面内容的缩写&#xff1a; render: function (createElement) {return createElement(App); } 进一步缩写为(ES6 语法)&#xff1a; render (createElement) {return createElement(App); } 再进一步缩写为&#xff1a; render (h){retur…

Linux第77步_处理Linux并发的相关函数

了解linux中的“原子整形数据”操作、“原子位数据”操作、自旋锁、读写锁、顺序锁、信号量和互斥体&#xff0c;以及相关函数。 并发就是多个“用户”同时访问同一个共享资源。如&#xff1a;多个线程同时要求读写同一个EEPROM芯片&#xff0c;这个EEPROM就是共享资源&#x…