震撼!通过双重异步,Excel 10万行数据导入从191秒优化到2秒!

震撼!通过双重异步,Excel 10万行数据导入从191秒优化到2秒!

在现代的企业级应用开发中,海量数据的处理效率和并发性能优化是一个非常重要的课题。无论是大规模数据导入、文件解析,还是在分布式系统中处理高并发任务,如何提升系统的处理速度、合理利用计算资源、减少线程上下文切换的开销,这些都是开发者必须面对的问题。在这一背景下,线程池技术以及异步编程逐渐成为提升系统性能的利器。

本文将深入探讨如何通过合理设计线程池和利用异步编程模型,有效优化大规模数据的处理性能。我们将结合 Spring Boot 框架中的 @Async 注解、自定义线程池、以及通过使用 EasyExcel 进行大数据量的 Excel 解析和异步写入数据库的场景,详细说明如何通过分而治之的策略,减少系统的响应时间、提高并发处理能力。同时,还将分析如何基于 CPU 和 IO 密集型任务的特性,来合理设置线程池的核心线程数、最大线程数等参数,以便在实际项目中能够充分发挥硬件资源的性能。

通常我是这样做的:

  1. 使用POI读取需要导入的Excel文件;

  2. 将文件名作为表名,列标题作为列名,并将数据拼接成SQL语句;

  3. 通过JDBC或Mybatis插入到数据库。

在操作中,如果文件数量多且数据量大,处理过程可能会非常缓慢。

访问后,感觉程序没有响应,但实际上,它正在读取并插入数据,只是速度很慢。

读取包含10万行的Excel文件竟然耗时191秒!

我以为程序卡住了!

private void readXls(String filePath, String filename) throws Exception {@SuppressWarnings("resource")XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream(filePath));// 读取第一个工作表XSSFSheet sheet = xssfWorkbook.getSheetAt(0);// 获取总行数int maxRow = sheet.getLastRowNum();StringBuilder insertBuilder = new StringBuilder();insertBuilder.append("insert into ").append(filename).append(" ( UUID,");XSSFRow row = sheet.getRow(0);for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {insertBuilder.append(row.getCell(i)).append(",");}insertBuilder.deleteCharAt(insertBuilder.length() - 1);insertBuilder.append(" ) values ( ");StringBuilder stringBuilder = new StringBuilder();for (int i = 1; i <= maxRow; i++) {XSSFRow xssfRow = sheet.getRow(i);String id = "";String name = "";for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {if (j == 0) {id = xssfRow.getCell(j) + "";} else if (j == 1) {name = xssfRow.getCell(j) + "";}}boolean flag = isExisted(id, name);if (!flag) {stringBuilder.append(insertBuilder);stringBuilder.append('\'').append(uuid()).append('\'').append(",");for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {stringBuilder.append('\'').append(value).append('\'').append(",");}stringBuilder.deleteCharAt(stringBuilder.length() - 1);stringBuilder.append(" )").append("\n");}}List<String> collect = Arrays.stream(stringBuilder.toString().split("\n")).collect(Collectors.toList());int sum = JdbcUtil.executeDML(collect);
}private static boolean isExisted(String id, String name) {String sql = "select count(1) as num from " + static_TABLE + " where ID = '" + id + "' and NAME = '" + name + "'";String num = JdbcUtil.executeSelect(sql, "num");return Integer.valueOf(num) > 0;
}private static String uuid() {return UUID.randomUUID().toString().replace("-", "");
}

如何优化?

优化1:首先,查询所有数据,将其缓存到map中,然后在插入前做决策。这样可以大大提高速度。

优化2:如果单个Excel文件太大,可以考虑使用异步和多线程,分批读取多行并插入数据库。

优化3:如果文件太多,可以为每个Excel文件使用一个异步进程,实现双重异步读取和插入。

使用双重异步处理后,从191秒优化到了2秒,你能相信吗?

以下是异步读取Excel文件和批量读取大Excel文件的关键代码。

异步读取缓存的Excel Controller类

@RequestMapping(value = "/readExcelCacheAsync", method = RequestMethod.POST)
@ResponseBody
public String readExcelCacheAsync() {String path = "G:\\Test\\data\\";try {// 读取Excel之前,缓存所有数据USER_INFO_SET = getUserInfo();File file = new File(path);String[] xlsxArr = file.list();for (int i = 0; i < xlsxArr.length; i++) {File fileTemp = new File(path + "\\" + xlsxArr[i]);String filename = fileTemp.getName().replace(".xlsx", "");readExcelCacheAsyncService.readXls(path + filename + ".xlsx", filename);}} catch (Exception e) {logger.error("|#ReadDBCsv|#Exception: ", e);return "error";}return "success";
}

批量读取超大Excel文件

@Async("async-executor")
public void readXls(String filePath, String filename) throws Exception {@SuppressWarnings("resource")XSSFWorkbook xssfWorkbook = new XSSFWorkbook(new FileInputStream(filePath));// 读取第一个工作表XSSFSheet sheet = xssfWorkbook.getSheetAt(0);// 总行数int maxRow = sheet.getLastRowNum();logger.info(filename + ".xlsx,共 " + maxRow + " 行数据!");StringBuilder insertBuilder = new StringBuilder();insertBuilder.append("insert into ").append(filename).append(" ( UUID,");XSSFRow row = sheet.getRow(0);for (int i = 0; i < row.getPhysicalNumberOfCells(); i++) {insertBuilder.append(row.getCell(i)).append(",");}insertBuilder.deleteCharAt(insertBuilder.length() - 1);insertBuilder.append(" ) values ( ");int times = maxRow / STEP + 1;for (int time = 0; time < times; time++) {int start = STEP * time + 1;int end = STEP * time + STEP;if (time == times - 1) {end = maxRow;}if (end + 1 - start > 0) {readExcelDataAsyncService.readXlsCacheAsyncMybatis(sheet, row, start, end, insertBuilder);}}
}

异步批量插入数据库

@Async("async-executor")
public void readXlsCacheAsync(XSSFSheet sheet, XSSFRow row, int start, int end, StringBuilder insertBuilder) {StringBuilder stringBuilder = new StringBuilder();for (int i = start; i <= end; i++) {XSSFRow xssfRow = sheet.getRow(i);String id = "";String name = "";for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {if (j == 0) {id = xssfRow.getCell(j) + "";} else if (j == 1) {name = xssfRow.getCell(j) + "";}}// 在读取Excel之前,先缓存所有数据,然后做决策boolean flag = isExisted(id, name);if (!flag) {stringBuilder.append(insertBuilder);stringBuilder.append('\'').append(uuid()).append('\'').append(",");for (int j = 0; j < row.getPhysicalNumberOfCells(); j++) {stringBuilder.append('\'').append(value).append('\'').append(",");}stringBuilder.deleteCharAt(stringBuilder.length() - 1);stringBuilder.append(" )").append("\n");}}List<String> collect = Arrays.stream(stringBuilder.toString().split("\n")).collect(Collectors.toList());if (collect != null && collect.size() > 0) {int sum = JdbcUtil.executeDML(collect);}
}private boolean isExisted(String id, String name) {return ReadExcelCacheAsyncController.USER_INFO_SET.contains(id + "," + name);
}

异步线程池工具类

@Async 的目的是异步处理任务。

  1. 在方法上添加 @Async 表明该方法是异步的。

  2. 在类上添加 @Async 表示该类中的所有方法都是异步的。

  3. 使用此注解的类必须由 Spring 管理。

  4. 必须在启动类或配置类中添加 @EnableAsync 注解,@Async 才能生效。

在使用 @Async 时,如果不指定线程池的名称,即不自定义线程池,默认会使用一个线程池。这个默认线程池是 Spring 的 SimpleAsyncTaskExecutor。

默认线程池的默认配置如下:

  1. 默认核心线程数:8。

  2. 最大线程数:Integer.MAX_VALUE。

  3. 队列类型:LinkedBlockingQueue。

  4. 容量:Integer.MAX_VALUE。

  5. 空闲线程保留时间:60秒。

  6. 线程池拒绝策略:AbortPolicy。

从最大线程数可以看出,在并发情况下,线程会无限制地创建。

你也可以通过 yml 文件重新配置:

spring:task:execution:pool:max-size: 10core-size: 5keep-alive: 3squeue-capacity: 1000thread-name-prefix: my-executor

你也可以自定义线程池。以下是使用 @Async 自定义线程池的简单代码实现:

@EnableAsync // 支持异步操作
@Configuration
public class AsyncTaskConfig {/*** 来自 com.google.guava 的线程池* @return*/@Bean("my-executor")public Executor firstExecutor() {ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();// 获取 CPU 处理器数量int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 100,200, TimeUnit.SECONDS,new LinkedBlockingQueue<>(), threadFactory);threadPool.allowsCoreThreadTimeOut();return threadPool;}/*** Spring 的线程池* @return*/@Bean("async-executor")public Executor asyncExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();// 核心线程数taskExecutor.setCorePoolSize(24);// 线程池维护的最大线程数,超出核心线程数的线程仅当缓冲队列满时才会创建taskExecutor.setMaxPoolSize(200);// 缓冲队列taskExecutor.setQueueCapacity(50);// 超出核心线程数的线程空闲时间,超时后将被销毁taskExecutor.setKeepAliveSeconds(200);// 异步方法内部线程名taskExecutor.setThreadNamePrefix("async-executor-");/*** 当线程池的任务缓存队列已满,且线程池中的线程数量已达到最大值时,如果还有任务到来,将采用任务拒绝策略。* 通常有以下四种策略:* ThreadPoolExecutor.AbortPolicy:抛弃任务并抛出 RejectedExecutionException 异常。* ThreadPoolExecutor.DiscardPolicy:抛弃任务,但不抛出异常。* ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列最前面的任务,然后尝试执行当前任务(重复此过程)。* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前任务,自动调用执行方法,直到成功。*/taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.initialize();return taskExecutor;}
}

异步失效的原因

  1. 被 @Async 注解的方法不是 public 的;

  2. 被 @Async 注解的方法的返回值类型只能是 void 或 Future;

  3. 被 @Async 注解的方法如果是静态的也会失效;

  4. 未添加 @EnableAsync 注解;

  5. 调用者和被 @Async 注解的方法不能在同一个类中;

  6. 对异步方法使用 @Transactional 是无效的,但对异步方法内调用的方法加上 @Transactional 是有效的。

线程池中设置核心线程数的问题

我尚未有时间详细探讨:在线程池中设置 CorePoolSize 和 MaxPoolSize 的最适宜和最高效的数量是多少。

借此机会进行了一些测试。

我记得有个关于 CPU 处理器数量的说法

将 CorePoolSize 设置为 CPU 处理器的数量时,效率最高吗?

// 获取 CPU 处理器数量
int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;

Runtime.getRuntime().availableProcessors() 会获取 CPU 核心线程数,代表计算资源。

  • 对于 CPU 密集型任务,线程池的大小设置为 N,与 CPU 线程数一致,这可以最大限度地减少线程间的上下文切换。但在实际开发中,一般设置为 N+1,以防止线程由于不可预见的情况而阻塞。如果发生阻塞,多出来的线程可以继续执行任务,保证 CPU 的高效利用。

  • 对于 IO 密集型任务,线程池的大小设置为 2N。这个数值是根据业务压力测试得出的,或者在不涉及业务时使用推荐值。

实际中,线程池的具体大小需要根据压力测试以及机器的当前状态进行调整。

如果线程池过大,会导致 CPU 持续切换,系统整体性能并不会有显著提高,反而可能会变慢。

我电脑的 CPU 处理器数量为 24。

那么一次读取多少行效率最高呢?

测试中,Excel 文件包含 10 万行数据。10 万 / 24 = 4166,因此我设置为 4200。这是最有效的设置吗?

测试过程中似乎的确如此。

我记得大家习惯性地将核心线程数(CorePoolSize)和最大线程数(MaxPoolSize)设置为相同的数值,通常是 200。

这只是随机选择,还是基于经验的?

测试发现,当 CorePoolSize 和 MaxPoolSize 都设置为 200 时,最初同时开启了 150 个线程工作。

为什么会这样呢?

经过数十次测试后

  1. 发现核心线程数并没有太大区别;

  2. 关键是每次读取和存储的行数,不能太多,存储速度会逐渐减慢;

  3. 也不能太少,如果少于 150 个线程,会导致线程阻塞,反而减慢进程。

IV.使用 EasyExcel 读取并插入数据库

我不会写 EasyExcel 的双异步优化。大家要记住避免掉进低级勤奋的陷阱。

ReadEasyExcelController

@RequestMapping(value = "/readEasyExcel", method = RequestMethod.POST)
@ResponseBody
public String readEasyExcel() {try {String path = "G:\\Test\\data\\";String[] xlsxArr = new File(path).list();for (int i = 0; i < xlsxArr.length; i++) {String filePath = path + xlsxArr[i];File fileTemp = new File(path + xlsxArr[i]);String fileName = fileTemp.getName().replace(".xlsx", "");List<UserInfo> list = new ArrayList<>();EasyExcel.read(filePath, UserInfo.class, new ReadEasyExeclAsyncListener(readEasyExeclService, fileName, batchCount, list)).sheet().doRead();}}catch (Exception e){logger.error("readEasyExcel Exception:",e);return "error";}return "success";
}

ReadEasyExeclAsyncListener

public ReadEasyExeclService readEasyExeclService;
// 表名
public String TABLE_NAME;
// 批量插入阈值
private int BATCH_COUNT;
// 数据收集
private List<UserInfo> LIST;public ReadEasyExeclAsyncListener(ReadEasyExeclService readEasyExeclService, String tableName, int batchCount, List<UserInfo> list) {this.readEasyExeclService = readEasyExeclService;this.TABLE_NAME = tableName;this.BATCH_COUNT = batchCount;this.LIST = list;
}@Override
public void invoke(UserInfo data, AnalysisContext analysisContext) {data.setUuid(uuid());data.setTableName(TABLE_NAME);LIST.add(data);if (LIST.size() >= BATCH_COUNT) {// 批量入库readEasyExeclService.saveDataBatch(LIST);}
}@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {if (LIST.size() > 0) {// 最后一批入库readEasyExeclService.saveDataBatch(LIST);}
}public static String uuid() {return UUID.randomUUID().toString().replace("-", "");
}

ReadEasyExeclServiceImpl

@Service
public class ReadEasyExeclServiceImpl implements ReadEasyExeclService {@Resourceprivate ReadEasyExeclMapper readEasyExeclMapper;@Overridepublic void saveDataBatch(List<UserInfo> list) {// Insert into the database via mybatisreadEasyExeclMapper.saveDataBatch(list);// Insert into the database via JDBC// insertByJdbc(list);list.clear();}private void insertByJdbc(List<UserInfo> list){List<String> sqlList = new ArrayList<>();for (UserInfo u : list){StringBuilder sqlBuilder = new StringBuilder();sqlBuilder.append("insert into ").append(u.getTableName()).append(" ( UUID,ID,NAME,AGE,ADDRESS,PHONE,OP_TIME ) values ( ");sqlBuilder.append("'").append(ReadEasyExeclAsyncListener.uuid()).append("',").append("'").append(u.getId()).append("',").append("'").append(u.getName()).append("',").append("'").append(u.getAge()).append("',").append("'").append(u.getAddress()).append("',").append("'").append(u.getPhone()).append("',").append("sysdate )");sqlList.add(sqlBuilder.toString());}JdbcUtil.executeDML(sqlList);}
}

UserInfo

@Data
public class UserInfo {private String tableName;private String uuid;@ExcelProperty(value = "ID")private String id;@ExcelProperty(value = "NAME")private String name;@ExcelProperty(value = "AGE")private String age;@ExcelProperty(value = "ADDRESS")private String address;@ExcelProperty(value = "PHONE")private String phone;
}

结语

在处理高并发、大数据导入等场景时,异步编程和线程池技术提供了一种极具效率的解决方案。通过合理配置线程池的核心线程数、最大线程数、队列长度等参数,能够在确保系统稳定性的前提下,大幅提升并发处理能力。而通过异步编程,我们可以有效避免线程阻塞、减少资源浪费,并让系统在面对大量请求时依然能够保持较高的响应速度。

本文的示例通过 Spring Boot 的 @Async 注解和自定义线程池,在实际的 EasyExcel 大数据导入场景下,验证了这种技术组合的高效性和实用性。此外,通过对 CPU 密集型任务和 IO 密集型任务的深入分析,开发者能够根据自身项目的特点,选择合适的线程池配置策略,最大化资源利用率和性能表现。

在实际应用中,线程池和异步编程不仅适用于大数据导入,还可以推广到包括文件处理、网络请求、日志处理等各类需要并发处理的场景中。因此,掌握并灵活运用这些技术,将为我们的系统性能优化提供坚实的基础,使我们能够应对更复杂、更苛刻的业务需求。

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

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

相关文章

Linux编程:用于调试 C、C++ 和其他编程语言编写的程序的调试工具GDB的使用

目录 一、概述 二、 安装GDB 三、准备程序 四、使用GDB 1、启动GDB 2、获取帮助 五、 常用GDB命令 六、示例调试会话 七、其他事项 一、概述 GDB&#xff08;GNU Debugger&#xff09;是一个非常强大的调试工具&#xff0c;广泛用于调试 C、C 和其他编程语言编写的程序…

书生实战营第四期-基础岛第五关-XTuner 微调个人小助手认知

基础任务 使用 XTuner 微调 InternLM2-Chat-7B 实现自己的小助手认知 一、环境配置与数据准备 1.构建虚拟环境 cd ~ #git clone 本repo git clone https://github.com/InternLM/Tutorial.git -b camp4 mkdir -p /root/finetune && cd /root/finetune conda create -…

java day04-面向对象基础(内存 封装 继承 修饰符 工具类 )

1.对象内存图 1.1 Java 内存分配 1.2 堆和栈 栈:所有局部变量都会在栈内存中创建 局部变量&#xff1a;定义在方法中的变量或者方法声明上的变量 方法执行都会加载到栈中进行 -----------------------------------------------------------------------------------------…

【C++练习】二进制到十进制的转换器

题目&#xff1a;二进制到十进制的转换器 描述 编写一个程序&#xff0c;将用户输入的8位二进制数转换成对应的十进制数并输出。如果用户输入的二进制数不是8位&#xff0c;则程序应提示用户输入无效&#xff0c;并终止运行。 要求 程序应首先提示用户输入一个8位二进制数。…

Pytorch学习--神经网络--网络模型的保存与读取

一、网络模型的保存与读取方式1 方法讲解 保存模型 import torch import torchvision model torchvision.models.vgg16(weightsDEFAULT) #保存模型和参数 torch.save(model,"save_method1.pth")读取模型 import torch model torch.load("save_method1.pth&…

凸优化理论,凸二次规划问题,对偶问题及KKT条件

凸优化理论 ​ 研究凸优化之前我们不妨提出几个小问题&#xff1a; 什么是优化问题&#xff1f;优化问题的解是什么&#xff1f;什么是凸优化问题&#xff1f;凸优化问题的解决方案是什么&#xff1f; 1.1 优化问题 ​ 理解优化问题其实很简单&#xff0c;我们其实从高中事…

智能的编织:C++中auto的编织艺术

在C的世界里&#xff0c;auto这个关键字就像是一个聪明的助手&#xff0c;它能够自动帮你识别变量的类型&#xff0c;让你的代码更加简洁和清晰。下面&#xff0c;我们就来聊聊auto这个关键字的前世今生&#xff0c;以及它在C11标准中的新用法。 auto的前世 在C11之前&#x…

The Rank-then-Encipher Approach

原始观点 Format-Preserving Encryption 4 The Rank-then-Encipher Approach 引用1 Hybrid diffusion-based visual image encryption for secure cloud storage 2.2 Sum-preserving encryption Bellare introduced the concept of format-preserving encryption (FPE)…

江西省补贴性线上职业技能培训管理平台(刷课系统)

江西省补贴性线上职业技能培训管理平台(刷课系统) 目的是为了刷这个网课 此系统有两个版本一个是脚本运行&#xff0c;另外一个是可视化界面运行 可视化运行 技术栈:flask、vue3 原理: 通过分析网站接口&#xff0c;对某些接口加密的参数进行逆向破解&#xff0c;从而修改请求…

Linux基础4-进程5(程序地址空间详解)

上篇文章:Linux基础4-进程4&#xff08;环境变量&#xff0c;命令行参数详解&#xff09;-CSDN博客 本章重点&#xff1a; 1 重新理解c/c地址空间 2 虚拟地址空间 一. c/c地址空间 地址空间布局图: 运行下列代码&#xff0c;进行观察 #include <stdio.h> #include <…

动态规划-背包问题——[模版]01背包(背包母题)

1.题目解析 题目来源 [模版]01背包_牛客题霸_牛客网 测试用例 2.算法原理 1.状态表示 第一小问&#xff1a;求最大价值 第二小问&#xff1a;求充满时的价值 2.状态转移方程 第一小问&#xff1a;求最大价值 第二小问&#xff1a;求充满时的价值 3.初始化 第一小问&#xff1a…

JavaWeb之会话跟踪技术

前言 这一节主要讲会话跟踪技术 1.补充 为了提交Gitee我修改了模块的目录&#xff0c;就是移动了模块&#xff0c;导致模块不是Maven了&#xff0c;可以在右边的Maven小工具&#xff0c;点加号&#xff0c;把模块重新添加为Maven 2. 概述 3. Cookie 3.1 基本使用 //发送coo…

第二十周周报:回顾篇

目录 摘要 Abstract 1 深度学习基础知识 1.1 学习率 1.1.1 自适应学习率 1.1.2 学习率调度 1.2 归一化 1.2.1 批量归一化 1.2.2 特征归一化 1.3 激活函数 1.3.1 Sigmoid函数 1.3.2 Tanh函数 1.3.3 ReLU函数 1.3.4 Leak ReLU函数 1.3.5 PReLU函数 1.3.6 ELU函数…

智能化SCRM方案助力企业高效管理与营销转型

内容概要 现代企业面临着复杂多变的市场环境&#xff0c;传统的管理与营销方式常常无法满足日益增长的需求。这时&#xff0c;智能化SCRM方案便应运而生&#xff0c;为企业带来了新的机遇与挑战。智能化SCRM方案不仅仅是一个单一的工具&#xff0c;它更像是一个全面的解决方案…

PRD2012学习笔记

图例位置&#xff1a; 使用 loc‘upper left’ 指定图例的基本位置为左上角。 使用 bbox_to_anchor(0.1, 0.9) 来进行自定义位置调整&#xff0c;其中 (0.1, 0.9) 指定图例相对于图形区域的坐标 (x, y)。 0.1 表示距离左边界的比例位置&#xff0c;0.9 表示距离上边界的比例位置…

【01课_初识算法与数据结构】

一、理解算法 1、算法的概念 算法&#xff0c;个人理解就是计算一段逻辑&#xff0c;最简化&#xff0c;最快速的方式、方法 每个函数&#xff0c;就包含了一定的算法&#xff0c;执行一定的计算逻辑 算法是一系列程序指令&#xff0c;用于解决特定的运算和逻辑问题 2、衡…

《⼆叉搜索树》

《⼆叉搜索树》 1. ⼆叉搜索树的概念2. ⼆叉搜索树的性能分析3 二叉树的功能说明及实现3.1 ⼆叉搜索树的插⼊3.2 ⼆叉搜索树的查找3.3 ⼆叉搜索树的删除 4二叉搜索树的实现代码5 ⼆叉搜索树key和key/value使⽤场景5.1 key搜索场景&#xff1a;5.2 key/value搜索场景&#xff1a…

stm32 踩坑笔记

串口问题&#xff1a; 问题&#xff1a;会改变接收缓冲的下一个字节 串口的初始化如下&#xff0c;位长度选择了9位。因为要奇偶校验&#xff0c;要选择9位。但是接收有用数据只用到1个字节。 问题原因&#xff1a; 所以串口接收时会把下一个数据更改

卫星授时服务器,单北斗授时服务器,北斗卫星时钟服务器

当前NTP授时服务器已经实现内部的元器件及芯片实现采用国产化&#xff0c;已经证明了国产产品已经摆脱需要依靠进口元器件及芯片才能实现的产品研发、也证明了大国崛起。下来我们来分析下国产化服务器具备的优势。 1、采用国产操作系统&#xff1a;使用国产化系统Linux更加可靠…

Windows11免密码自动登录

按winR&#xff0c;打开运行&#xff0c;输入Control Userpasswords2&#xff0c;打开用户账户。 打开该设置&#xff0c;取消选中该选项&#xff0c;点击应用&#xff0c;输入想要自动登录的账户和密码&#xff0c;即可开机后自动登录Windows。 若此界面无该选项&#xff0c;…