springboot kafka 实现延时队列

好文推荐:
2.5万字详解23种设计模式
基于Netty搭建websocket集群实现服务器消息推送
2.5万字讲解DDD领域驱动设计

文章目录

  • 一、延时队列定义
  • 二、应用场景
  • 三、技术实现方案:
    • 1. Redis
    • 2. Kafka
    • 3. RabbitMQ
    • 4. RocketMQ
  • 四、Kafka延时队列背景
  • 五、Kafka延时队列实现思路
  • 六、Kafka延时队列架构图
  • 七、kafka延时任务代码实现
    • 1. KafkaDelayQueue:Kafka延迟队列
    • 2. KafkaDelayQueueFactory:Kafka延迟队列工厂
    • 3. KafkaPollListener:Kafka延迟队列事件监听
    • 4. KafkaDelayConfig:Kafka延时配置
  • 八. 如何使用kafka延时队列
  • 九、测试
  • 十、总结

在这里插入图片描述

一、延时队列定义

延时队列:是一种消息队列,可以用于在指定时间或经过一定时间后执行某种操作。

小编已经做好了Kafka延时队列的封装,以后只需要一行代码就可以实现kafka延时队列了,代码中有详细注释,完整代码已经给大家整理好了,领取方式放在了文章末。

二、应用场景

1,订单超时自动取消:用户下单后,如果在指定时间内未完成支付,系统会自动取消订单,释放库存。
2,定时推送:比如消息通知,用户预约某个服务,系统会在服务开始前一定时间发送提醒短信。
3,定时任务:将需要定时执行的任务放入延时队列中,等到指定的时间到达时再进行执行,例如生成报表、统计数据等操作。
4,限时抢购:将限时抢购的结束时间放入延时队列中,当时间到达时自动下架商品。

三、技术实现方案:

1. Redis

1.1 优点:
①Redis的延迟队列是基于Redis的sorted set实现的,性能较高。
②Redis的延迟队列可以通过TTL设置过期时间,灵活性较高。
③简单易用,适用于小型系统。
④性能较高,支持高并发。1.2 缺点:
①可靠性相对较低,可能会丢失消息,就算redis最高级别的持久化也是有可能丢一条的,每次请求都做aof,但是aof是异步的,所以不保证这一条操作能被持久化。
②而且Redis持久化的特性也导致其在数据量较大时,存储和查询效率逐渐降低,此时会需要对其进行分片和负载均衡。
③Redis的延迟队列需要手动实现消息重试机制,更严谨的消息队列需要数据库兜底。1.3 应用场景:
①适用于较小规模的系统,实时性要求较高的场景。
②适用于轻量级的任务调度和消息通知场景,适合短期延迟任务,不适合长期任务,例如订单超时未支付等。

2. Kafka

2.1 优点:
①Kafka的优点在于其高并发、高吞吐量和可扩展性强,同时支持分片。
②可靠性高,支持分布式和消息持久化。
③消费者可以随时回溯消费。
④支持多个消费者并行消费、消费者组等机制。2.2 缺点:
①没有原生的延迟队列功能,需要使用topic和消费者组来实现,实现延迟队列需要额外的开发工作。
②消费者需要主动拉取数据,可能会导致延迟,精度不是特别高。
在此案例中代码已经实现了,直接拿来使用就可以了。2.3 应用场景:
适用于大规模的数据处理,实时性要求较高的,高吞吐量的消息处理场景。

3. RabbitMQ

3.1 优点:
①RabbitMQ的延迟队列是通过RabbitMQ的插件实现的,易于部署和使用。
②RabbitMQ的延迟队列支持消息重试和消息顺序处理,可靠性较高。
③支持消息持久化和分布式。
④支持优先级队列和死信队列。
⑤提供了丰富的插件和工具。3.2 缺点:
①RabbitMQ的延迟队列性能较低,不适用于高吞吐量的场景。
②性能较低,不适合高并发场景。
③实现延迟队列需要额外的配置,但是配置就很简单了。3.3应用场景:
适用于中小型的任务调度和消息通知,对可靠性要求高的场景。

4. RocketMQ

4.1 优点:
①RocketMQ的延迟队列是RocketMQ原生支持的,易于使用和部署。
②RocketMQ的延迟队列支持消息重试和消息顺序处理,可靠性较高。
③高性能和高吞吐量,支持分布式和消息持久化。
④RocketMQ使用简单,性能好,并且支持延迟队列功能。4.2 缺点:
①RocketMQ的延迟队列不支持动态添加或删除队列。
②RocketMQ的延迟队列需要保证消息的顺序,可能会导致消息延迟。
③在节点崩溃后,RocketMQ有可能发生消息丢失。4.3 应用场景:
①适用于大规模的数据处理,对性能和吞吐量要求较高的场景。
②适合于任务量较大、需要延迟消息和定时消息的场景。例如电商平台、社交软件等。
③适用于分布式任务调度和高可靠性消息通知场景。

四、Kafka延时队列背景

  1. 基于以上四种实现延时队列的分析来,选择对应的技术方案的基础上呢,不同公司的mq的基础设施不同,如果只有Kafka,也没必要引入RabbitMQ和RocketMq来实现,引入新的组件也会顺便带来新的问题。

  2. 网上搜Kafka实现延时队列有很多文章,很多文章说使用Kafka内部的时间轮,支持延时操作,但这是Kafka自己内部使用的,时间轮只是一个工具类,用户无法将其作为延迟队列来使用。

  3. Kafka延时队列的最佳实践,使用Kafka消费者的暂停和恢复机制来实现

五、Kafka延时队列实现思路

  1. 解决一个问题前首先要明确问题,如何让Kafka有延时队列的功能呢?
  2. 就是在Kafka消费者消费的时候延时消费,不久搞定了嘛
  3. 那如何延时消费呢,网上有些文章使用Thread.sleep进行延时消费这是不靠谱的(亲身实践),sleep的时间超过了Kafka配置的max.poll.records时间,消费者无法及时提交offset,kafka就会认为这个消费者已经挂了,会进行rebalance也就是重新分配分区给消费者,以保证每个分区只被一个消费者消费
  4. 也有同学说了,为了不发生rebalance,那可以增加max.poll.records时间啊,但是这样的话,如果要sleep几天的时间,难道max.poll.records要写几天的时间嘛,有违Kafka的设计原理了,那怎么办呢?
  5. 这时候Kafka的pause暂停消费和resume恢复消费就登场了,pause暂停某个分区之后消费者不会再poll拉取该分区的消息,直到resume恢复该分区之后才会重新poll消息。
  6. 我已经做好了Kafka延时队列的封装,以后只需要一行代码就可以实现延时队列了,代码核心使用Kafka消费者的pause函数(暂停)和resume函数(恢复)+线程池+定时任务+事件监听机制+工厂模式

六、Kafka延时队列架构图

在这里插入图片描述

七、kafka延时任务代码实现

以下代码只列出了核心实现,完整代码已经给大家整理好了,可以关注【微信公众号】微信搜索【老板来一杯java】,然后【加群】直接获取源码,在自己项目中引入即用!
源码目录:
在这里插入图片描述

1. KafkaDelayQueue:Kafka延迟队列

定义一个Kafka延期队列,包含的内容:KafkaDelayQueue,其中有延迟队列配置,主题,消费组,延迟时间,目标主题,KafkaSyncConsumer,ApplicationContext,poll线程池,delay线程池等等

package com.wdyin.kafka.delay;import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.common.TopicPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.ThreadPoolExecutor;/*** kafka延时队列** @Author WDYin* @Date 2022/7/2**/
@Slf4j
@Getter
@Setter
class KafkaDelayQueue<K, V> {private String topic;private String group;private Integer delayTime;private String targetTopic;private KafkaDelayConfig kafkaDelayConfig;private KafkaSyncConsumer<K, V> kafkaSyncConsumer;private ApplicationContext applicationContext;private ThreadPoolTaskScheduler threadPoolPollTaskScheduler;private ThreadPoolTaskScheduler threadPoolDelayTaskScheduler;......
}

2. KafkaDelayQueueFactory:Kafka延迟队列工厂

Kafka延期队列的工厂,用于及其管理延迟队列

package com.wdyin.kafka.delay;import lombok.Data;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;import java.util.Properties;/*** 延时队列工厂* @author WDYin* @date 2023/4/17**/
@Data
public class KafkaDelayQueueFactory {private KafkaDelayConfig kafkaDelayConfig;private Properties properties;private ApplicationContext applicationContext;private Integer concurrency;public KafkaDelayQueueFactory(Properties properties, KafkaDelayConfig kafkaDelayConfig) {Assert.notNull(properties, "properties cannot null");Assert.notNull(kafkaDelayConfig.getDelayThreadPool(), "delayThreadPool cannot null");Assert.notNull(kafkaDelayConfig.getPollThreadPool(), "pollThreadPool cannot null");Assert.notNull(kafkaDelayConfig.getPollInterval(), "pollInterval cannot null");Assert.notNull(kafkaDelayConfig.getPollTimeout(), "timeout cannot null");this.properties = properties;this.kafkaDelayConfig = kafkaDelayConfig;}public void listener(String topic, String group, Integer delayTime, String targetTopic) {if (StringUtils.isEmpty(topic)) {throw new RuntimeException("topic cannot empty");}if (StringUtils.isEmpty(group)) {throw new RuntimeException("group cannot empty");}if (StringUtils.isEmpty(delayTime)) {throw new RuntimeException("delayTime cannot empty");}if (StringUtils.isEmpty(targetTopic)) {throw new RuntimeException("targetTopic cannot empty");}KafkaSyncConsumer<String, String> kafkaSyncConsumer = createKafkaSyncConsumer(group);KafkaDelayQueue<String, String> kafkaDelayQueue = createKafkaDelayQueue(topic, group, delayTime, targetTopic, kafkaSyncConsumer);kafkaDelayQueue.send();}private KafkaDelayQueue<String, String> createKafkaDelayQueue(String topic, String group, Integer delayTime, String targetTopic, KafkaSyncConsumer<String, String> kafkaSyncConsumer) {KafkaDelayQueue<String, String> kafkaDelayQueue = new KafkaDelayQueue<>(kafkaSyncConsumer, kafkaDelayConfig);Assert.notNull(applicationContext, "kafkaDelayQueue need applicationContext");kafkaDelayQueue.setApplicationContext(applicationContext);kafkaDelayQueue.setDelayTime(delayTime);kafkaDelayQueue.setTopic(topic);kafkaDelayQueue.setGroup(group);kafkaDelayQueue.setTargetTopic(targetTopic);return kafkaDelayQueue;}private KafkaSyncConsumer<String, String> createKafkaSyncConsumer(String group) {properties.put(ConsumerConfig.GROUP_ID_CONFIG, group);return new KafkaSyncConsumer<>(properties);}}

3. KafkaPollListener:Kafka延迟队列事件监听

package com.wdyin.kafka.delay;import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import org.springframework.context.ApplicationListener;
import org.springframework.kafka.core.KafkaTemplate;import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;/*** 延时队列监听* @Author : WDYin* @Date : 2021/5/7* @Desc :*/
@Slf4j
public class KafkaPollListener<K, V> implements ApplicationListener<KafkaPollEvent<K, V>> {private KafkaTemplate kafkaTemplate;public KafkaPollListener(KafkaTemplate kafkaTemplate) {this.kafkaTemplate = kafkaTemplate;}@Overridepublic void onApplicationEvent(KafkaPollEvent<K, V> event) {ConsumerRecords<K, V> records = (ConsumerRecords<K, V>) event.getSource();Integer delayTime = event.getDelayTime();KafkaDelayQueue<K, V> kafkaDelayQueue = event.getKafkaDelayQueue();KafkaSyncConsumer<K, V> kafkaSyncConsumer = kafkaDelayQueue.getKafkaSyncConsumer();Set<TopicPartition> partitions = records.partitions();Map<TopicPartition, OffsetAndMetadata> commitMap = new HashMap<>();partitions.forEach((partition) -> {List<ConsumerRecord<K, V>> consumerRecords = records.records(partition);for (ConsumerRecord<K, V> record : consumerRecords) {long startTime = (record.timestamp() / 1000) * 1000;long endTime = startTime + delayTime;long now = System.currentTimeMillis();if (endTime > now) {kafkaSyncConsumer.pauseAndSeek(partition, record.offset());kafkaDelayQueue.getThreadPoolPollTaskScheduler().schedule(kafkaDelayQueue.delayTask(partition), new Date(endTime));break;}log.info("{}: partition:{}, offset:{}, key:{}, value:{}, messageDate:{}, nowDate:{}, messageDate:{}, nowDate:{}",Thread.currentThread().getName() + "#" + Thread.currentThread().getId(), record.topic() + "-" + record.partition(), record.offset(), record.key(), record.value(), LocalDateTime.ofInstant(Instant.ofEpochMilli(startTime), ZoneId.systemDefault()), LocalDateTime.now(), startTime, Instant.now().getEpochSecond());kafkaTemplate.send(kafkaDelayQueue.getTargetTopic(), record.value());commitMap.put(partition, new OffsetAndMetadata(record.offset() + 1));}});if (!commitMap.isEmpty()) {kafkaSyncConsumer.commit(commitMap);}}
}

4. KafkaDelayConfig:Kafka延时配置

package com.wdyin.kafka.delay;import lombok.Data;/*** 延时队列配置* @author WDYin* @date 2023/4/16**/
@Data
public class KafkaDelayConfig {private Integer pollInterval;private Integer pollTimeout;private Integer pollThreadPool;private Integer delayThreadPool;public KafkaDelayConfig() {}......
}

八. 如何使用kafka延时队列

自己项目中引入以上代码之后,使用KafkaDelayApplication:一个Kafka延迟任务注册程序,注意一个延时主题对应一个延迟时间,后续有新的延迟任务只需要在此注册延迟任务的监听即可!开箱即用!
使用流程:

  1. 生产者发送消息到【延时主题】——自己写
  2. 然后Kafka将消息从【延时主题】经过【延时时间】后发送到【目标主题】——以下代码
  3. 自己创建消费者消费【目标主题】——自己写
import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;
import javax.annotation.Resource;/*** @author WDYin* @date 2023/4/18**/
@Component
public class KafkaDelayApplication {@Resourceprivate KafkaDelayQueueFactory kafkaDelayQueueFactory;/*** 延迟任务都可以配置在这里* Kafka将消息从【延时主题】经过【延时时间】后发送到【目标主题】*/@PostConstructpublic void init() {//延迟30秒kafkaDelayQueueFactory.listener("delay-30-second-topic", "delay-30-second-group", 1 * 30 * 1000, "delay-60-second-target-topic");//延迟60秒kafkaDelayQueueFactory.listener("delay-60-second-topic", "delay-60-second-group", 1 * 60 * 1000, "delay-60-second-target-topic");//延迟30分钟kafkaDelayQueueFactory.listener("delay-30-minute-topic", "delay-30-minute-group", 30 * 60 * 1000, "delay-30-minute-target-topic");}
}

九、测试

  1. 先往延时主题【delay-60-second-topic】发送一千条消息,一共10个分区,每个分区100条消息,消息时间是2023-04-21 16:37:26分,延迟消息消费时间就应该是2023-04-21 16:38:26
    在这里插入图片描述
  2. 延时队列进行消费:通过日志查看,消息日期和延迟队列消费消息时间正好相差一分钟
    在这里插入图片描述

十、总结

  1. 本案例已成功实现Kafka的延时队列,并进行实测,代码引入可用非常方便。
  2. Kafka实现的延时队列支持秒级别的延时任务,不支持毫秒级别,但是毫秒级别的延时任务也没有意义
  3. 注意一个主题对应的延时时间是一致的,不能在同一个主题里放不同时间的延时任务。
  4. 此方案的缺点就是,如果数据量极大,生产者生产消息速度很快,一定要保证Kafka的消费能力,否则可能会导致延迟,精度不是特别高,不过如果延迟秒级的任务,差个几毫秒肯定可以接受的,一般场景肯定满足。
  5. 完整代码已经给大家整理好了,可以关注【微信公众号】微信搜索【老板来一杯java】,然后【加群】直接获取源码。

好文推荐:
2.5万字详解23种设计模式
微服务springcloud环境下基于Netty搭建websocket集群实现服务器消息推送----netty是yyds
2.5万字讲解DDD领域驱动设计

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

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

相关文章

锐捷VSU和M-LAG介绍

参考网站 堆叠、级联和集群的概念 什么是堆叠&#xff1f; 框式集群典型配置 RG-S6230[RG-S6501-48VS8CQ]系列交换机 RGOS 12.5(4)B1005版本 配置指南 总结 根据以上的几篇文章总结如下&#xff1a; 级联&#xff1a;简单&#xff0c;交换机相连就叫级联&#xff0c;跟搭…

HCIA--路由优先级实验

要求&#xff1a; 1. pc1访问pc3,4,5走上面&#xff0c;R1-R2实现备份21.1.1.0/24实现备份&#xff1b; 2. pc3,4,5,6访问pc1,2走下面&#xff0c; R3,4之间实现等价路由&#xff0c;尽量减少路由条目&#xff0c;实现全网可达&#xff0c;pc7代表运营商 所有pc均可访问 1…

5.0 ZooKeeper 数据模型 znode 结构详解

数据模型 在 zookeeper 中&#xff0c;可以说 zookeeper 中的所有存储的数据是由 znode 组成的&#xff0c;节点也称为 znode&#xff0c;并以 key/value 形式存储数据。 整体结构类似于 linux 文件系统的模式以树形结构存储。其中根路径以 / 开头。 进入 zookeeper 安装的 …

阅读笔记——《RapidFuzz: Accelerating fuzzing via Generative Adversarial Networks》

【参考文献】Ye A, Wang L, Zhao L, et al. Rapidfuzz: Accelerating fuzzing via generative adversarial networks[J]. Neurocomputing, 2021, 460: 195-204.【注】本文仅为作者个人学习笔记&#xff0c;如有冒犯&#xff0c;请联系作者删除。 目录 摘要 一、介绍 二、相关…

寒假作业-day4

1>请编程实现哈希表的创建存储数组{12,24,234,234,23,234,23}&#xff0c;输入key查找的值&#xff0c;实现查找功能。 代码&#xff1a; #include<stdio.h> #include<stdlib.h> #include<string.h> #include<math.h> typedef int datatype; type…

C++ PE文件信息解析

尝试解析PE文件结构, 于是编写了此PE信息助手类, 暂时完成如下信息解析 1.导入表信息 2.导入表信息 3.资源表信息 CPEHelper.h #pragma once// // brief: PE文件解析助手类 // copyright: Copyright 2024 FlameCyclone // license: // birth: Created by Visual Studio 20…

软件价值8-站点连通性检查

站点连通性检查&#xff0c;即看网站是否能访问得通&#xff0c;实用价值不大&#xff0c;不过用来作软件应用入门还不错。 代码&#xff1a; import urllib.request import tkinter as tkdef test_connectivity():window tk.Tk()window.geometry(600x400)window.resizable(F…

输出超级玛丽2_题解

【题解提供者】吴立强 解法 思路 本题代码非常简单&#xff0c;直接一行一行对齐后输出即可&#xff0c;只是比较麻烦。 代码展示 #include <iostream> using namespace std;int main() {printf(" ********\n");printf(" …

普通编程,机器学习与深度学习

普通编程&#xff1a;基于人手动设置规则&#xff0c;由输入产生输出经典机器学习&#xff1a;人手工指定需要的特征&#xff0c;通过一些数学原理对特征与输出的匹配模式进行学习&#xff0c;也就是更新相应的参数&#xff0c;从而使数学表达式能够更好的根据给定的特征得到准…

Java 学习和实践笔记(1)

2024年&#xff0c;决定好好学习计算机语言Java. B站上选了这个课程&#xff1a;【整整300集】浙大大佬160小时讲完的Java教程&#xff08;学习路线Java笔记&#xff09;零基础&#xff0c;就从今天开始学吧。 在这些语言中&#xff0c;C语言是最基础的语言&#xff0c;绝大多…

Blender_pmx导出fbx

Blender_pmx导出fbx 学无止境&#xff1f; 相关链接&#xff1a; Blender教程&#xff1a; Blender中文手册介绍 — Blender Manualhttps://docs.blender.org/manual/zh-hans/2.79/about/introduction.htmlhttps://www.blendercn.org/https://www.blendercn.org/Blender下载…

1978-2023年全国整体GDP平减指数计算模板(含计算公式代码+计算结果且可任意调整基期)

1978-2023年全国整体GDP平减指数、实际GDP数据&#xff08;可任意调整基期&#xff09; 1、时间&#xff1a;1978-2023年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;名义GDP、实际GDP、国内生产总值指数、GDP平减指数 4、数据内含原始数据计算公式代码&…

JVM 性能调优 - JVM 参数基础(2)

查看 JDK 版本 $ java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) 查看 Java 帮助文档 $ java -help 用法: java [-options] class [args...] …

牛客“迎新春,过大年”多校程序设计竞赛A题

题目描述&#xff1a; 这里有个小trick 当时也看到数据范围的问题了 n 是 1 e 6 ∑ i 1 n a [ i ] < 5 e 7 n是1e6 \quad \sum_{i1}^na[i]<5e7 n是1e6∑i1n​a[i]<5e7 我们考虑不同的数 1 2 . . . k − 1 k 1 \quad 2 \quad ... k-1 \quad k 12...k−1k s u m …

【UE 材质】扇形材质

目录 效果 步骤 &#xff08;1&#xff09;控制扇形的弧宽度 &#xff08;2&#xff09;控制扇形的角度 &#xff08;3&#xff09;完整节点 效果 步骤 &#xff08;1&#xff09;控制扇形的弧宽度 创建一个材质&#xff0c;混合模式设置为“Additive”&#xff0c;着色…

论文阅读——MP-Former

MP-Former: Mask-Piloted Transformer for Image Segmentation https://arxiv.org/abs/2303.07336 mask2former问题是&#xff1a;相邻层得到的掩码不连续&#xff0c;差别很大 denoising training非常有效地稳定训练时期之间的二分匹配。去噪训练的关键思想是将带噪声的GT坐标…

第2节、让电机转起来【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;本节介绍用简单的方式&#xff0c;让步进电机转起来。其目的之一是对电机转动有直观的感受&#xff0c;二是熟悉整个开发流程。本系列教程必要的51单片机基础包括IO口操作、中断、定时器三个部分&#…

紫光展锐M6780丨用MEMC捕捉每帧精彩

在之前的系列专栏里&#xff0c;我们揭秘了紫光展锐首颗AI8K超高清智能显示芯片平台M6780的AI-PQ、AI-SR以及Local Dimming技术。今天&#xff0c;揭秘M6780让急速动态画面清晰无拖影的绝招——MEMC&#xff08;运动估计与运动补偿技术&#xff09;。 刷新率是电视画质评估维度…

【算法】枚举——蓝桥杯、日期统计、特殊日期(位数之和)、2023、特殊日期(倍数)、跑步锻炼

文章目录 蓝桥杯日期统计特殊日期&#xff08;位数之和&#xff09;2023特殊日期&#xff08;倍数&#xff09;跑步锻炼 蓝桥杯 日期统计 日期统计 如果暴力枚举100个数的八次循环那就是1016次运算&#xff0c;时间复杂度太高了&#xff0c;好在前四次的2023是确定的&#xf…

Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(一)

Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画&#xff0c;Kotlin&#xff08;一&#xff09; 基于Matrix&#xff0c;控制Bitmap的setRectToRect的目标RectF的宽高。从很小的宽高开始&#xff0c;不断迭代增加setRectToRect的目标RectF的宽高&#xff0c…