2024.2.6 模拟实现 RabbitMQ —— 数据库操作
目录
引言
选择数据库
环境配置
设计数据库表
实现流程
封装数据库操作
针对 DataBaseManager 单元测试
引言
- 硬盘保存分为两个部分
- 数据库:交换机(Exchange)、队列(Queue)、绑定(Binding)
- 文件:消息(Message)
选择数据库
- MySQL 数据库是比较重量的数据库!
- 此处为了使用更方便,简化环境,采取的数据库是更轻量的 SQLite 数据库
原因:
- 一个完整的 SQLite 数据库,只有一个单独的可执行文件(不到 1M)
- MySQL 是客户端服务器结构的程序,而 SQLite 只是一个本地的数据库,相当于是直接操作本地的硬盘文件
注意:
- SQLite 数据库应用非常广泛,在一些性能不高的设备上,SQLite 数据库是首选
- 尤其是移动端和嵌入式设备 (Android 系统就是内置的 SQLite)
环境配置
- 在 Java 中要想使用 SQLite 数据库,无需额外安装,直接使用 Maven,将 SQLite 的依赖直接引入进来即可!
- 此时 Maven 依赖会自动加载 jar 包和 动态库文件
<!-- https://mvnrepository.com/artifact/org.xerial/sqlite-jdbc --> <dependency><groupId>org.xerial</groupId><artifactId>sqlite-jdbc</artifactId><version>3.41.0.1</version> </dependency>
- 编写 yml 配置文件(此处我们使用 MyBatis 操作 SQLite 数据库)
spring:datasource:url: jdbc:sqlite:./data/meta.dbusername:password:driver-class-name: org.sqlite.JDBCmybatis:mapper-locations: classpath:mapper/**Mapper.xml
注意点一:
- SQLite 数据库将数据存储在当前硬盘的某个指定的文件中(./data/meta.db)
注意点二:
- 谈到相对路径,就需要明确 "基准路径" "工作路径"
- 如果是在 IDEA 中直接运行程序,此时工作路径就是当前项目所在的路径
- 如果是通过 javr -jar 方式运行部署的,此时你在哪个目录下执行的命令,哪个目录就是工作路径
注意点三:
- 对于 SQLite 数据库来说,并不需要指定用户名密码
- MySQL 数据库是一个客户端服务器结构的程序,而一个数据库服务器,就会对应很多个客户端来访问它
- 相比之下,SQLite 则不是客户端服务器结构的程序,其数据放在本地文件上,与网络无关,只有本地主机才能访问
注意点四:
- SQLite 虽然和 MySQL 不太一样,但是都可以通过 MyBatis 这样的框架来使用
注意点五:
- 当把上述的配置和依赖都准备好了之后,程序启动便会自动建库!
设计数据库表
- 需要在数据库中存储的有 交换机(Exchange)、队列(Queue)、绑定(Binding)
- 对照着上述这样的核心类,很容易把这几个表设计出来的
问题:
- 上述表的建表操作,具体什么时机来执行?
回答:
- 以往写的程序,都是先将数据库表啥的创建好,再启动服务器
- 即 将建库建表语句写到一个 .sql 文件中,需要建表时,直接复制到 MySQL 客户端中执行即可
- 这个操作都是在部署阶段完成的
- 之前大概就部署一次即可,不会反复操作,但是后续接触到的更多的程序可能会涉及到反复部署多次
- 综上,通过代码自动完成建表操作,简化部署步骤,也是十分关键的!
实现流程
- 创建一个 interface 接口,描述有哪些方法要给 Java 代码使用
import com.example.demo.mqserver.core.Binding; import com.example.demo.mqserver.core.Exchange; import com.example.demo.mqserver.core.MSGQueue; import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper public interface MetaMapper { // 提供三个核心建表方法void createExchangeTable();void createQueueTable();void createBindingTable();// 针对上述三个基本概念,进行 插入 和 删除void insertExchange(Exchange exchange);List<Exchange> selectAllExchanges();void deleteExchange(String exchangeName);void insertQueue(MSGQueue msgQueue);List<MSGQueue> selectAllQueues();void deleteQueue(String queueName);void insertBinding(Binding binding);List<Binding> selectAllBindings();void deleteBinding(Binding binding); }
- 创建对应的 xml 文件,通过 xml 来实现上述 interface 接口中的抽象方法
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.mqserver.mapper.MetaMapper"><update id="createExchangeTable">create table if not exists exchange (name varchar(50) primary key,type int,durable boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createQueueTable">create table if not exists queue (name varchar(50) primary key,durable boolean,exclusive boolean,autoDelete boolean,arguments varchar(1024));</update><update id="createBindingTable">create table if not exists binding (exchangeName varchar(50),queueName varchar(50),bindingKey varchar(256));</update><insert id="insertExchange" parameterType="com.example.demo.mqserver.core.Exchange">insert into exchange values(#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});</insert><select id="selectAllExchanges" resultType="com.example.demo.mqserver.core.Exchange">select * from exchange;</select><delete id="deleteExchange" parameterType="java.lang.String">delete from exchange where name = #{exchangeName};</delete><insert id="insertQueue" parameterType="com.example.demo.mqserver.core.MSGQueue">insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});</insert><select id="selectAllQueues" resultType="com.example.demo.mqserver.core.MSGQueue">select * from queue;</select><delete id="deleteQueue" parameterType="java.lang.String">delete from queue where name = #{queueName};</delete><insert id="insertBinding" parameterType="com.example.demo.mqserver.core.Binding">insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey});</insert><select id="selectAllBindings" resultType="com.example.demo.mqserver.core.Binding">select * from binding;</select><delete id="deleteBinding" parameterType="com.example.demo.mqserver.core.Binding">delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};</delete> </mapper>
注意点一:
- 此处我们使用 update 标签来实现建表操作!
问题:
- 当前是将每个建表语句都单独的列为一个 update 标签,并且对应一个 Java 方法
- 此处我们能否改成一个 update 标签中包含多个建表语句,同时借助一个 Java 方法,完成上述多个表的创建呢?
回答:
- MyBatis 支持一个 标签 中包含多个 sql 语句,其前提为,搭配 MySQL 或 Oracle 使用
- 对于 SQLite 来说,是无法做到上述功能的
- 即 当一个 update 标签中,写了多个 create table 语句时,只有第一个语句能执行
注意点二:
- Exchange 和 Queue 这两个表,由于使用 name 做为主键,直接按照 name 进行删除即可
- 但对于 Binding 来说,此时没有主键,其删除操作是针对 exchangeName 和 queueName 两个纬度进行筛选
问题:
- 如果实现把 argument 键值对 与 数据库中的字符串类型相互转换呢?
回答:
- 关键要点在于,MyBatis 在完成数据库操作时,会自动的调用到对象的 getter 和 setter
- 比如 MyBatis 往数据库中写数据时,就会调用对象的 getter 方法,拿到属性值再往数据库中写
- 如果这个过程中,让 getArgument 得到的结果为 String 类型,此时,就可以直接把这个数据写到数据库了
- 比如 MyBatis 从数据库读数据时,就会调用对象的 setter 方法,将数据库中读到的结果设置到对象的属性中
- 如果这个过程中,让 setArgument 的参数为 String 类型,并且在 setArguments 内部针对字符串解析,解析成一个 Map 对象
- 综上,我们直接在包含 argument 成员变量的 Exchange 实体类 和 MSGQueue 实体类中,改写 argument 的 getter 和 setter 方法即可
// 这里的 getter setter 用于和数据库进行交互public String getArguments() { // 是把当前的 arguments 参数,从 Map 转成 String (JSON)ObjectMapper objectMapper = new ObjectMapper();try {return objectMapper.writeValueAsString(arguments);} catch (JsonProcessingException e) {e.printStackTrace();} // 如果代码真异常了,返回一个空的 json 字符串就 okreturn "{}";}// 这个方法,是从数据库读数据之后,构造 Exchange 对象,会自动调用到public void setArguments(String argumentsJson) { // 把参数中的 argumentsJson 按照 JSON 格式解析,转成上述的 Map 对象ObjectMapper objectMapper = new ObjectMapper();try {this.arguments = objectMapper.readValue(argumentsJson, new TypeReference<HashMap<String,Object>>() {});} catch (JsonProcessingException e) {e.printStackTrace();}}
注意:
- 此处的第二个参数用来描述当前 JSON 字符串,要转成的 Java 对象是啥类型的
- 如果是个简单类型,直接使用对应类型的类对象即可
- 如果是集合类这样的复杂类型,可以使用 TypeReference 匿名内部类对象,来描述复杂类型的具体信息(通过泛型参数来描述的)
封装数据库操作
- 此处我们将专门写一个类来整合上述的数据库操作
import com.example.demo.DemoApplication; import com.example.demo.mqserver.core.Binding; import com.example.demo.mqserver.core.Exchange; import com.example.demo.mqserver.core.ExchangeType; import com.example.demo.mqserver.core.MSGQueue; import com.example.demo.mqserver.mapper.MetaMapper;import java.io.File; import java.util.List;/* * 通过这个类,来整合上述的数据库操作 * */ public class DataBaseManager { // 从 Spring 中拿到现成的对象private MetaMapper metaMapper;// 针对数据库进行初始化public void init() { // 手动的获取到 MetaMappermetaMapper = DemoApplication.context.getBean(MetaMapper.class);if (!checkDBExists()) { // 如果数据库不存在,就进行建库建表操作 // 先创建一个 data 目录File dataDir = new File("./data");dataDir.mkdir(); // 创建数据库createTable(); // 插入默认数据createDefaultData();System.out.println("[DataBaseManager] 数据库初始化完成!");}else { // 数据库已经存在了,啥都不做即可System.out.println("[DataBaseManager] 数据库已经存在!");}}public void deleteDB() {File file = new File("./data/meta.db");boolean ret = file.delete();if(ret) {System.out.println("[DataBaseManager] 删除数据库文件成功!");}else {System.out.println("[DataBaseManager] 删除数据库文件失败!");}File dataDir = new File("./data"); // 使用 delete 删除目录的时候,需要保证目录是空的ret = dataDir.delete();if(ret) {System.out.println("[DataBaseManager] 删除数据库目录成功!");}else {System.out.println("[DataBaseManager] 删除数据库目录失败!");}}private boolean checkDBExists() {File file = new File("./data/meta.db");if(file.exists()) {return true;}return false;}// 这个方法用来建表 // 建库操作并不需要手动执行(不需要手动创建 meta.db 文件) // 首次执行这里的数据库操作的时候,就会自动的创建出 meta.db 文件来(MyBatis 帮我们完成的)private void createTable() {metaMapper.createExchangeTable();metaMapper.createQueueTable();metaMapper.createBindingTable();System.out.println("[DataBaseManager] 创建表完成!");}// 给数据库表中,添加默认的数据 // 此处主要是添加一个默认的交换机 // RabbitMQ 里有一个这样的设定: 带有一个 匿名 的交换机,类型是 DIRECTprivate void createDefaultData() { // 构造一个默认的交换机Exchange exchange = new Exchange();exchange.setName("");exchange.setType(ExchangeType.DIRECT);exchange.setDurable(true);exchange.setAutoDelete(false);metaMapper.insertExchange(exchange);System.out.println("[DataBaseManager] 创建初始数据成功!");}// 把其他的数据库操作,也在这个类中封装一下public void insertExchange(Exchange exchange) {metaMapper.insertExchange(exchange);}public List<Exchange> selectAllExchanges() {return metaMapper.selectAllExchanges();}public void deleteExchange(String exchangeName) {metaMapper.deleteExchange(exchangeName);}public void insertQueue(MSGQueue queue) {metaMapper.insertQueue(queue);}public List<MSGQueue> selectAllQueues() {return metaMapper.selectAllQueues();}public void deleteQueue(String queueName) {metaMapper.deleteQueue(queueName);}public void insertBinding(Binding binding) {metaMapper.insertBinding(binding);}public List<Binding> selectAllBindings (){return metaMapper.selectAllBindings();}public void deleteBinding(Binding binding) {metaMapper.deleteBinding(binding);} }
注意点一:
- 谈到初始化,我们一般都会用到 构造方法
- 但是此处的 init() 为一个普通方法
- 构造方法一般是用来初始化类的属性,即一般不太会涉及到太多的业务逻辑
- 但是此处的初始化是带有业务逻辑的,还是单独拎出来,手动来调用比较合适一些
注意点二:
- 数据库初始化 = 建库建表 + 插入一些默认数据
- 此处我们期望在 broker server 启动时,做出下列逻辑判定:
- 如果数据库已经存在了,即表啥的都有了,则不做任何操作
- 如果数据库不存在,则创建库,创建表,构造默认数据
实例理解
- 例如,现在将 broker server 部署到一个新的服务器上
- 显然,此时是没有数据库的,需让 broker server 启动时,自动将对应的数据库创建好
- 但是如果是一个已经部署过的机器,当 broker server 重启时,就会发现数据库已经有了,此时将不做任何数据库相关操作
- 综上,判定数据库是否存在,就等同于判定 meta.db 这个文件是否存在即可!
注意点三:
- 当然手动获取 MetaMapper 的对象前提是改写启动类代码
针对 DataBaseManager 单元测试
- 在设计单元测试时,要求单元测试用例 与 用例之间需相互独立,互不干扰
实例理解
- 测试用例A => 测试过程中,给数据库里插入了一些数据
- 测试用例B => 再针对 B 进行测试,可能 A 这里的数据,就会对 B 造成干扰
- 测试用例C => 再针对 C 测试,A 和 B 都可能因为数据库的数据影响到 C
注意:
- 此处的影响不一定是数据库,也可能是其他方面,如是否搞了个文件,是否占用了端口 等
解决方案:
- 每个用例执行之前,先执行一段逻辑,搭建测试环境,准备好测试需要用到的一些东西
- 每个用例执行之后,再执行一段逻辑,把用例执行过程中产生的中间结果,一些影响,给消除掉
- 综上,我们可以使用 @BeforeEach 和 @AfterEach 这两个注解
import com.example.demo.mqserver.core.Binding; import com.example.demo.mqserver.core.Exchange; import com.example.demo.mqserver.core.ExchangeType; import com.example.demo.mqserver.core.MSGQueue; import com.example.demo.mqserver.datacenter.DataBaseManager; 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 org.springframework.boot.test.context.SpringBootTest;import java.util.List;//加上这个注解之后,该类就会被识别为单元测试类 @SpringBootTest public class DataBaseManagerTests {private DataBaseManager dataBaseManager = new DataBaseManager();// 接下来下面这里需要编写多个 方法,每个方法都是一个/一组单元测试用例 // 还需要做一个准备工作,需要写两个方法,分别用于 "准备工作" 和 "收尾工作"// 使用这个方法,来执行准备工作,每个用例执行前,都要调用这个方法\@BeforeEachpublic void setUp() { // 由于在 init 中,需要通过 context 对象拿到 metaMapper 实例的 // 所以就需要先把 context 对象给搞出来DemoApplication.context = SpringApplication.run(DemoApplication.class);dataBaseManager.init();}// 使用这个方法,来执行收尾工作,每个用例执行后,都要调用这个方法@AfterEachpublic void tearDown() { // 这里要进行的操作,就是把数据库给清空(把数据库文件,meta.db 直接删了就行) // 注意,此处不能直接就删除,而需要先关闭上述 context 对象! // 此处的 context 对象,持有了 Meta 的实例, MetaMapper 实例又打开了 meta.db 数据库文件 // 如果 Meta.db 被别人打开了,此时的删除文件操作是不会成功的(Windows 系统的限制,Linux 则没有这个问题) // 另一方面,获取 context 操作,会占用 8080 端口,此处的 close 也是释放 8080DemoApplication.context.close();dataBaseManager.deleteDB();}@Testpublic void testInitTable() { // 由于 init 方法,已经在上面 setUp 中掉用过了,直接在测试用例代码中,检查当前的数据库状态即可 // 直接从数据库中查询,看数据是否符合预期 // 查交换机表,里面应该有一个数据(匿名的 exchange); 查队列,没有数据; 查绑定表,没有数据;List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();List<MSGQueue> queueList = dataBaseManager.selectAllQueues();List<Binding> bindingList = dataBaseManager.selectAllBindings();// 直接打印结果,通过肉眼,固然也可以,但是不优雅,不方便 // 更好的办法是使用断言 // System.out.println(exchangeList.size()); // assertEquals 判定结果是不是相等 // 注意这俩参数的顺序,虽然比较相等,谁在前谁在后,无所谓 // 但是 assertEquals 的形参,第一个形参叫做 expected(预期的),第二个形参叫做 actual(实际的)Assertions.assertEquals(1,exchangeList.size());Assertions.assertEquals("",exchangeList.get(0).getName());Assertions.assertEquals(ExchangeType.DIRECT,exchangeList.get(0).getType());Assertions.assertEquals(0,queueList.size());Assertions.assertEquals(0,bindingList.size());}private Exchange createTestExchange(String exchangeName) {Exchange exchange = new Exchange();exchange.setName(exchangeName);exchange.setType(ExchangeType.FANOUT);exchange.setAutoDelete(false);exchange.setDurable(true);exchange.setArguments("aaa",1);exchange.setArguments("bbb",2);return exchange;}@Testpublic void testInsertExchange() { // 构造一个 Exchange 对象,插入到数据库中,再查询出来,看结果是否符合预期Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange); // 插入完毕之后,查询结果List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(2,exchangeList.size());Exchange newExchange = exchangeList.get(1);Assertions.assertEquals("testExchange",newExchange.getName());Assertions.assertEquals(ExchangeType.FANOUT,newExchange.getType());Assertions.assertEquals(false,newExchange.isAutoDelete());Assertions.assertEquals(true,newExchange.isDurable());Assertions.assertEquals(1,newExchange.getArguments("aaa"));Assertions.assertEquals(2,newExchange.getArguments("bbb"));}@Testpublic void testDeleteExchange() { // 先构造一个交换机,插入数据库,然后再按照名字删除即可!Exchange exchange = createTestExchange("testExchange");dataBaseManager.insertExchange(exchange);List<Exchange> exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(2,exchangeList.size());Assertions.assertEquals("testExchange",exchangeList.get(1).getName());// 进行删除操作dataBaseManager.deleteExchange("testExchange"); // 再次查询exchangeList = dataBaseManager.selectAllExchanges();Assertions.assertEquals(1,exchangeList.size());Assertions.assertEquals("",exchangeList.get(0).getName());}private MSGQueue createTestQueue(String queueName) {MSGQueue queue = new MSGQueue();queue.setName(queueName);queue.setDurable(true);queue.setAutoDelete(false);queue.setExclusive(false);queue.setArguments("aaa",1);queue.setArguments("bbb",2);return queue;}@Testpublic void testInsertQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(1,queueList.size());MSGQueue newQueue = queueList.get(0);Assertions.assertEquals("testQueue",newQueue.getName());Assertions.assertEquals(true,newQueue.isDurable());Assertions.assertEquals(false,newQueue.isAutoDelete());Assertions.assertEquals(false,newQueue.isAutoDelete());Assertions.assertEquals(1,newQueue.getArguments("aaa"));Assertions.assertEquals(2,newQueue.getArguments("bbb"));}@Testpublic void testDeleteQueue() {MSGQueue queue = createTestQueue("testQueue");dataBaseManager.insertQueue(queue);List<MSGQueue> queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(1,queueList.size()); // 进行删除dataBaseManager.deleteQueue("testQueue");queueList = dataBaseManager.selectAllQueues();Assertions.assertEquals(0,queueList.size());}private Binding createTestBinding(String exchangeName,String queueName) {Binding binding = new Binding();binding.setExchangeName(exchangeName);binding.setQueueName(queueName);binding.setBindingKey("testBindingKey");return binding;}@Testpublic void testInsertBinding() {Binding binding = createTestBinding("testExchange","testQueue");dataBaseManager.insertBinding(binding);List<Binding> bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(1,bindingList.size());Assertions.assertEquals("testExchange",bindingList.get(0).getExchangeName());Assertions.assertEquals("testQueue",bindingList.get(0).getQueueName());Assertions.assertEquals("testBindingKey",bindingList.get(0).getBindingKey());}@Testpublic void testDeleteBinding() {Binding binding = createTestBinding("testExchange","testQueue");dataBaseManager.insertBinding(binding);List<Binding> bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(1,bindingList.size());// 删除Binding toDeleteBinding = createTestBinding("testExchange","testQueue");dataBaseManager.deleteBinding(toDeleteBinding);bindingList = dataBaseManager.selectAllBindings();Assertions.assertEquals(0,bindingList.size());} }
注意点一:
- 相比于功能/业务代码,测试用例代码,编写起来是比较无聊的
- 但是重要性是非常大的!
- 这些操作会大大提高整个项目的开发效率!
- 写代码,不太可能没有 bug,进行周密的测试,是应对 bug 的最有效手段
相关文章:

2024.2.6 模拟实现 RabbitMQ —— 数据库操作
目录 引言 选择数据库 环境配置 设计数据库表 实现流程 封装数据库操作 针对 DataBaseManager 单元测试 引言 硬盘保存分为两个部分 数据库:交换机(Exchange)、队列(Queue)、绑定(Binding࿰…...

dolphinscheduler海豚调度(一)简介快速体验
1、简介 Apache DolphinScheduler 是一个分布式易扩展的可视化DAG工作流任务调度开源系统。适用于企业级场景,提供了一个可视化操作任务、工作流和全生命周期数据处理过程的解决方案。 Apache DolphinScheduler 旨在解决复杂的大数据任务依赖关系,并为应…...

VTK 三维场景的基本要素(相机) vtkCamera
观众的眼睛好比三维渲染场景中的相机,在VTK中用vtkCamera类来表示。vtkCamera负责把三维场景投影到二维平面,如屏幕,相机投影示意图如下图所示。 1.与相机投影相关的要素主要有如下几个: 1)相机位置: 相机所处的位置…...

小游戏和GUI编程(5) | SVG图像格式简介
小游戏和GUI编程(5) | SVG图像格式简介 0. 问题 Q1: SVG 是什么的缩写?Q2: SVG 是一种图像格式吗?Q3: SVG 相对于其他图像格式的优点和缺点是什么?Q4: 哪些工具可以查看 SVG 图像?Q5: SVG 图像格式的规范是怎样的?Q6…...

多机多卡运行nccl-tests和channel获取
nccl-tests 环境1. 安装nccl2. 安装openmpi3. 单机测试4. 多机测试mpirun多机多进程多节点运行nccl-testschannel获取 环境 Ubuntu 22.04.3 LTS (GNU/Linux 5.15.0-91-generic x86_64)cuda 11.8 cudnn 8nccl 2.15.1NVIDIA GeForce RTX 4090 *2 1. 安装nccl #查看cuda版本 nv…...

SQL,HQL刷题,尚硅谷
相关表数据: 1、score_info 2、student_info 题目及思路解析: 分组结果的条件 1、查询平均成绩大于60分的学生的学号和平均成绩 代码: selectstu_id,avg(score) score_avg from score_info group by stu_id having score_avg>60; 思路…...

DevOps:CI、CD、CB、CT、CD
目录 一、软件开发流程演化快速回顾 (一)瀑布模型 (二)原型模型 (三)螺旋模型 (四)增量模型 (五)敏捷开发 (六)DevOps 二、走…...
[leetcode经典算法题]删除有序数组中的重复项(双指针)
删除有序数组中的重复项 给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素…...
【国产MCU】-CH32V307-触摸按键检测(TKEY)
触摸按键检测(TKEY) 文章目录 触摸按键检测(TKEY)1、TKEY介绍2、TKEY使用实例触摸检测控制(TKEY)单元,借助ADC 模块的电压转换功能,通过将电容量转换为电压量进行采样,实现触摸按键检测功能。检测通道复用ADC 的16 个外部通道,通过ADC 模块的单次转换模式实现触摸按键…...

Hive的小文件问题
目录 一、小文件产生的原因 二、小文件的危害 三、小文件的解决方案 3.1 小文件的预防 3.1.1 减少Map数量 3.1.2 减少Reduce的数量 3.2 已存在的小文件合并 3.2.1 方式一:insert overwrite (推荐) 3.2.2 方式二:concatenate 3.2.3 方式三ÿ…...

攻防世界——re2-cpp-is-awesome
64位 我先用虚拟机跑了一下这个程序,结果输出一串字符串flag ——没用 IDA打开后 F5也没有什么可看的 那我们就F12查看字符串找可疑信息 这里一下就看见了 __int64 __fastcall main(int a1, char **a2, char **a3) {char *v3; // rbx__int64 v4; // rax__int64 v…...

问山海——天涯海角——桃花渊boss攻击顺序
文章目录 桃花渊代码代码解读代码执行结果攻击顺序示意图 桃花渊 规划击杀各个boss顺序。 副本持续时间为30分钟,每个地方的boss被打死后,需要一定时间才能重新刷新。 只考虑其中两种boss,龟将和龟龙。各有四个。 其中我从一个boss地点到…...

springboot181基于springboot的乐享田园系统
简介 【毕设源码推荐 javaweb 项目】基于springbootvue 的 适用于计算机类毕业设计,课程设计参考与学习用途。仅供学习参考, 不得用于商业或者非法用途,否则,一切后果请用户自负。 看运行截图看 第五章 第四章 获取资料方式 **项…...
Dubbo集成Zookeeper embbed模式
为了简化应用支持服务方便的分合,使用Zookeeper embbed模式。集成Zookeeper比较容易,使用starter或自己写代码都可以。但是由于集成了Dubbo,每次启动时都会发现zookeeper没有启动就开始报错退出,但是确是已经集成了。 于是只能翻…...

156基于Matlab的光纤陀螺随机噪声和信号
基于Matlab的光纤陀螺随机噪声和信号,利用固定步长和可调步长的LMS自适应滤波、最小二乘法、滑动均值三种方法进行降噪处理,最后用阿兰方差评价降噪效果。程序已调通,可直接运行。 156 信号处理 自适应滤波 降噪效果评估 (xiaohongshu.com)...

秋招上岸大厂,分享一下经验
文章目录 秋招过程学习过程项目经验简历经验面试经验offer选择总结 秋招过程 今天是除夕,秋招已经正式结束了,等春节过完就到了春招的时间点了。 运气比较好,能在秋招的末尾进入一家大厂,拿到20k的sp offer。 从九月份十月份就开…...
使用 C++23 从零实现 RISC-V 模拟器
👉🏻 文章汇总「从零实现模拟器、操作系统、数据库、编译器…」:https://okaitserrj.feishu.cn/docx/R4tCdkEbsoFGnuxbho4cgW2Yntc 使用 C23 从零实现 RISC-V 模拟器 使用 C23 从零实现的 RISC-V 模拟器,最终的模拟器可以运行 x…...

Hugging Face 刚刚推出了一款开源的 AI 助手制造工具,直接向 OpenAI 的定制 GPT 挑战
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
powershell 雅地关闭UDP监听器
在PowerShell中优雅地关闭UDP监听器意味着你需要一种机制来安全地停止正在运行的UdpClient实例。由于UdpClient类本身没有提供直接的停止或关闭方法,你需要通过其他方式来实现这一点。通常,这涉及到在监听循环中添加一个检查点,以便在接收到停…...

Google Cloud 2024 年报告重点介绍了关键的网络威胁和防御
Google Cloud 的 2024 年威胁范围报告预测了云安全的主要风险,并提出了加强防御的策略。 该报告由 Google 安全专家撰写,为寻求预测和应对不断变化的网络安全威胁的云客户提供了宝贵的资源。 该报告强调,凭证滥用、加密货币挖矿、勒索软件和…...

《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
引言:探索视频播放程序设计之旅 在当今数字化时代,多媒体应用已渗透到我们生活的方方面面,从日常的视频娱乐到专业的视频监控、视频会议系统,视频播放程序作为多媒体应用的核心组成部分,扮演着至关重要的角色。无论是在个人电脑、移动设备还是智能电视等平台上,用户都期望…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...

Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...

2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...

数据分析六部曲?
引言 上一章我们说到了数据分析六部曲,何谓六部曲呢? 其实啊,数据分析没那么难,只要掌握了下面这六个步骤,也就是数据分析六部曲,就算你是个啥都不懂的小白,也能慢慢上手做数据分析啦。 第一…...
2025.6.9总结(利与弊)
凡事都有两面性。在大厂上班也不例外。今天找开发定位问题,从一个接口人不断溯源到另一个 接口人。有时候,不知道是谁的责任填。将工作内容分的很细,每个人负责其中的一小块。我清楚的意识到,自己就是个可以随时替换的螺丝钉&…...