Springboot + vue3 实现大文件上传方案:秒传、断点续传、分片上传、前端异步上传
参考:https://juejin.cn/post/6870837414852886542#heading-9
一般计算大文件的md5都是前端来做,因为如果后端来做,那得等到上传成功后才能计算md5值,并且读取的时间也很长。
为了解决文件大传输慢的问题,前端可以通过分片读取的方式来读取文件,并进行分片,将分片的数据传给后端,当传输完成后,由后端完成合并转码的操作。
tips:计算md5和分片传输本质都是分片来完成的,但不应该放一起或有关联,因为只有计算完md5值后我们才知道后端有没有这个大文件,从而实现秒传;有关联是指不应该把读取后的数据放到全局变量中,在分片传输时直接用不需要读,因为这样会导致客户端内存过大
实现了大文件上传的分片、合并、秒传、断点续传
前端
计算大文件md5
使用spark-md5来计算大文件的md5值,Spark-md5实现了在浏览器中对文件进行哈希计算,每次会将填充后的消息和64位表示原始消息长度的数拼接到一起,形成一个新的消息,这也就不会造成读取大文件的内存非常大,我们只需要对该大文件进行分片读并将读取到的分片数据给到 Spark-md5 帮我们计算
计算hash值
抽样计算hash值
代码和策略如下:
/*** 计算文件的hash值,计算的时候并不是根据所用的切片的内容去计算的,那样会很耗时间,我们采取下面的策略去计算:* 1. 第一个和最后一个切片的内容全部参与计算* 2. 中间剩余的切片我们分别在前面、后面和中间取2个字节参与计算* 这样做会节省计算hash的时间*/
const calculateHash = async (fileChunks: Array<{file: Blob}>) => {return new Promise(resolve => {const spark = new sparkMD5.ArrayBuffer()const chunks: Blob[] = []fileChunks.forEach((chunk, index) => {if (index === 0 || index === fileChunks.length - 1) {// 1. 第一个和最后一个切片的内容全部参与计算chunks.push(chunk.file)} else {// 2. 中间剩余的切片我们分别在前面、后面和中间取2个字节参与计算// 前面的2字节chunks.push(chunk.file.slice(0, 2))// 中间的2字节chunks.push(chunk.file.slice(CHUNK_SIZE / 2, CHUNK_SIZE / 2 + 2))// 后面的2字节chunks.push(chunk.file.slice(CHUNK_SIZE - 2, CHUNK_SIZE))}})const reader = new FileReader()reader.readAsArrayBuffer(new Blob(chunks))reader.onload = (e: Event) => {spark.append(e?.target?.result as ArrayBuffer)resolve(spark.end())}})
}
计算全量hash值
异步处理
//计算文件的md5值
const computeMd5 = (fileItem) => {let file = fileItem.filelet blobSize = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice //获取file对象的slice方法,确保在每个浏览器都能获取到const fileSize = file.sizelet chunks = computeChunks(fileSize)let currentChunkIndex = 0 //当前读到第几个分片,最开始为0let spark = new SparkMD5.ArrayBuffer();let fileReader = new FileReader();let loadNext = () => {let start = currentChunkIndex * chunkSizelet end = start + chunkSize >= fileSize ? fileSize : start + chunkSizefileReader.readAsArrayBuffer(blobSize.call(file, start, end))}loadNext()return new Promise((resolve, reject) => {let resultFile = getFileItemByUid(file.uid)fileReader.onload = (e) => { //读取成功后调用spark.append(e.target.result)currentChunkIndex++if (currentChunkIndex < chunks) { //继续分片 let percent = Math.floor(currentChunkIndex / chunks * 100)resultFile.md5Progress = percentloadNext()}else{let md5 = spark.end()spark.destroy()resultFile.md5Progress = 100 resultFile.status = STATUS.uploading.valueresultFile.md5 = md5loadNext = nullresolve(resultFile.file.uid)}}fileReader.onerror = (e) =>{ //读取出错调用fileItem.md5Progress = 1fileItem.status = STATUS.fail.valueloadNext = nullresolve(resultFile.file.uid)}}).catch(error=>{loadNext = nullconsole.log(error);return null});
}
web workder 线程处理
这里我们使用js中的多线程worker来计算md5,因为计算md5值很耗费cpu并且会影响页面的性能同时它不能通过异步来解决效率问题,只能一段一段的读取、计算
//hash.js
import SparkMD5 from 'spark-md5'
import { ElMessage } from 'element-plus'const computeChunks = (totalSize, chunkSize) => {return Math.ceil(totalSize / chunkSize)
}
// 生成文件 hash
self.onmessage = e => {let { fileItem } = e.datalet file = fileItem.filelet blobSize = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice //获取file对象的slice方法,确保在每个浏览器都能获取到const fileSize = file.sizeconst chunkSize = 1024 * 1024 * 10 //每个分片10Mlet chunks = computeChunks(fileSize, chunkSize)let currentChunkIndex = 0 //当前读到第几个分片,最开始为0let spark = new SparkMD5.ArrayBuffer();let fileReader = new FileReader();let loadNext = () => {let start = currentChunkIndex * chunkSizelet end = start + chunkSize >= fileSize ? fileSize : start + chunkSizefileReader.readAsArrayBuffer(blobSize.call(file, start, end))}loadNext()fileReader.onload = (e) => { //读取成功后调用spark.append(e.target.result)currentChunkIndex++if (currentChunkIndex < chunks) { //继续分片 let percent = Math.floor(currentChunkIndex / chunks * 100)self.postMessage({percentage: percent,});loadNext()} else {let md5 = spark.end()spark.destroy()self.postMessage({md5,percentage: 100});loadNext = nullself.close(); // 关闭 worker 线程,线程如果不关闭,则会一直在后台运行着,}}fileReader.onerror = (e) => { //读取出错调用console.log(e);self.close();ElMessage.error('读取文件出错')loadNext = null}
};
主线程代码:
let worker = new Worker(new URL('./hash.js', import.meta.url))worker.postMessage({ fileItem: fileItem })fileItem = getFileItemByUid(fileItem.file.uid)worker.onmessage = function (event) {const { md5, percentage } = event.datafileItem.md5Progress = percentageif (md5 != null) {fileItem.status = STATUS.uploading.valuefileItem.md5 = md5callBack(fileItem.file.uid)}}worker.onerror = function (event) {console.log(event);worker.terminate() //出错后关闭子线程}
主线程负责开启子线程并给出文件信息,及时拿到子线程计算的结果即可
分片传输
根据分片结果异步调用后台分片接口实现分片传输
后端
/*** 分片上传接口*/@PostMapping("/common/uploadFile")public BaseResponse<UploadFileInfoResp> commonUploadFile(MultipartFile multipartFile,String fileMd5, //使用md5值来命名临时目录Integer chunkIndex, //当前是第几个分片Integer chunks //总共有多少个分片){if(chunkIndex >= chunks){return ResultUtils.error(CommonErrorEnum.BUSINESS_ERROR.getErrorCode(),"当前分片大于等于总分片");}UploadFileInfoResp uploadFileInfoResp = new UploadFileInfoResp();File tempFileFolder = null;boolean fileIsSuccess = true; // 文件是否上传成功标志,默认成功try{//暂存临时目录String tempFolderName = filePath + tempFolder;tempFileFolder = new File(tempFolderName+fileMd5);if(!tempFileFolder.exists()){ //创建该目录tempFileFolder.mkdirs();}File newFile = new File(tempFileFolder.getPath() + "/" + chunkIndex);if(newFile.exists() && newFile.length() == multipartFile.getSize()){ //断点续传,不需要在重新上传uploadFileInfoResp.setFileStatus(UploadStatusEnum.UPLOADING.getStatus());return ResultUtils.success(uploadFileInfoResp);}multipartFile.transferTo(newFile);uploadFileInfoResp.setFileStatus(UploadStatusEnum.UPLOADING.getStatus());return ResultUtils.success(uploadFileInfoResp);}catch (Exception e){log.error("文件上传失败 ",e);fileIsSuccess = false;}finally {if(!fileIsSuccess && Objects.nonNull(tempFileFolder)){ // 失败,删除临时目录FileUtil.del(tempFileFolder);}}return ResultUtils.success(uploadFileInfoResp);}
后端根据前端的分片信息和数据建一个临时目录,目录以计算的MD5值为命名,并将前端传输的分片的文件先上传到服务器上
前端
前端根据分片大小对大文件进行分片,得到总共有多少片,循环异步向后端发送分片上传请求
const chunkSize = 1024 * 1024 * 10 //每个分片10M
const computeChunks = (totalSize) => {return Math.ceil(totalSize / chunkSize)
}
//执行分片上传逻辑
const uploadFile = async (fileUid, fromChunkIndex) => {let fileItem = getFileItemByUid(fileUid)if (fileItem == undefined) returnif (fromChunkIndex == null) { //如果不是点击暂停继续上传const secondResult = await queryUploadFileApi(fileItem.md5)if (secondResult != null && secondResult.fileStatus == STATUS.upload_seconds.value) { //秒传fileItem.status = STATUS[secondResult.fileStatus].valuefileItem.uploadProgress = 100return;}}let chunkIndex = fromChunkIndex || 0let file = fileItem.filelet fileSize = file.size//分片上传let chunks = computeChunks(fileSize)const taskPool = []const maxTask = 6 //最大异步处理数量for (let i = chunkIndex; i < chunks; i++) {fileItem = getFileItemByUid(fileUid)if (fileItem == null || fileItem.pause) { //处理删除或暂停逻辑await Promise.all(taskPool)if(fileItem != null)recordAllStopChunkIndex.push({uid: file.uid,chunkIndex: i}) //记录暂停的具体信息return;}let start = i * chunkSizelet end = start + chunkSize >= fileSize ? fileSize : start + chunkSizelet chunkFile = file.slice(start, end)const task = uploaderFileApi({file: chunkFile, chunkIndex: i, chunks: chunks, fileMd5: fileItem.md5})task.then(res => {if (res.code == undefined && res.fileStatus === 'uploading' && fileItem != null) { //计算上传速度fileItem.uploadSize = fileItem.uploadSize + chunkFile.sizefileItem.uploadProgress = Math.floor((fileItem.uploadSize / fileSize) * 100)}taskPool.splice(taskPool.findIndex((item) => item === task),1) //清除已经完成的分片请求}).catch(error => { console.log(error); taskPool.splice(taskPool.findIndex((item) => item === task),1) })taskPool.push(task)if (taskPool.length >= maxTask) {await Promise.race(taskPool) //race方法会在这些请求中第一个完成后结束这个阻塞代码,限制最大请求数量}}await Promise.all(taskPool) //将剩下的分片发送并等待所有分片完成
}
因为这里用到了异步请求,那么就需要控制异步请求的最大并发数量,否则就会因为异步请求过多而对页面造成卡顿,我们可以使用异步的race函数,当有一个请求完成后就返回,循环结束后最后在等待未完成的分片上传完毕就可以执行合并操作了
合并
后端
后端根据临时目录的所有分片数据将他们进行合并,操作:依次按分片文件名从小到大读取每个分片文件,并将它写入到一个新的文件里
分片的文件名是按分片的顺序命名的
/*** 合并分片文件接口*/@PostMapping("/union/uploadFile")public BaseResponse<String> unionFile(@Valid UnionFileReq unionFileReq){ //合并文件String tempFolderName = null;try {String fileName = unionFileReq.getFileName();String fileMd5 = unionFileReq.getFileMd5();//文件夹路径String monthDay = DateUtil.format(new Date(),"yyyy-MM-dd");//真实的文件名String realFileName = IdUtils.simpleUUID() + MimeTypeUtils.getSuffix(fileName);//路径String newFilePath = monthDay + "/" + realFileName;//临时目录tempFolderName = filePath + tempFolder + fileMd5;//目标目录String targetFolderName = filePath + monthDay;File targetFolder = new File(targetFolderName);if(!targetFolder.exists()){targetFolder.mkdirs();}//目标文件String targetFileName = filePath + newFilePath;union(tempFolderName,targetFileName,realFileName,true);//合并成功,可以在这里记录数据库}catch (Throwable e){if(Objects.nonNull(tempFolderName)){ // 删除临时目录FileUtil.del(tempFolderName);}return ResultUtils.error(ErrorCode.OPERATION_ERROR);}return ResultUtils.success("ok");}public void union(String dirPath, String toFilePath, String fileName, Boolean del){File dir = new File(dirPath);if(!dir.exists()){throw new BusinessException("目录不存在");}File[] fileList = dir.listFiles(); //获取该目录的所有文件// 按文件名从小到大排序,确保合并顺序正确Arrays.sort(fileList, new Comparator<File>() {@Overridepublic int compare(File file1, File file2) {Integer value1 = Integer.valueOf(file1.getName());Integer value2 = Integer.valueOf(file2.getName());return value1 - value2;}});File targetFile = new File(toFilePath);RandomAccessFile writeFile = null;try{writeFile = new RandomAccessFile(targetFile,"rw");byte [] b = new byte[10 * 1024] ;//一次读取1MBfor (int i = 0; i < fileList.length; i++) {File chunkFile = new File(dirPath + "/" +i);RandomAccessFile readFile = null;try {readFile = new RandomAccessFile(chunkFile,"r");while ( readFile.read(b) != -1 ){writeFile.write(b,0, b.length);}}catch (Exception e){log.error("合并分片失败",e);throw new BusinessException("合并分片失败");}finally {if(Objects.nonNull(readFile))readFile.close();}}} catch (Exception e) {log.error("合并文件:{}失败",fileName,e);throw new RuntimeException(e);}finally {try {if(Objects.nonNull(writeFile))writeFile.close();if(del && dir.exists()){ // 删除临时目录FileUtil.del(dir);}} catch (IOException e) {e.printStackTrace();}}}
前端
在等待所有分片上传完成后调用后端合并接口即可
也就是在原有上传函数在加上一个调用合并接口操作
//所有分片上传完成,执行合并操作unionUploadFileApi({ fileName: file.name, fileMd5: fileItem.md5,pid: fileItem.filePid }).then(res => {if (res == "ok") {fileItem.uploadProgress = 100fileItem.status = STATUS["upload_finish"].value} else {fileItem.status = STATUS["fail"].valuefileItem.errorMsg = res.message}}).catch(error => { console.log(error); fileItem.status = STATUS["fail"].value; fileItem.errorMsg = '合并失败' })
秒传
后端
根据md5去查询数据库有没有上传过即可
/*** 查询大文件的md5值,秒传逻辑*/@PostMapping("/query/uploadFile")public BaseResponse<UploadFileInfoResp> queryUploadFile(String fileMd5){UploadFileInfoResp uploadFileInfoResp = new UploadFileInfoResp();UploadFile queryByMd5 = uploadFileDao.queryByMd5(fileMd5);//秒传if(Objects.nonNull(queryByMd5)){uploadFileInfoResp.setFileStatus(UploadStatusEnum.UPLOAD_SECONDS.getStatus());return ResultUtils.success(uploadFileInfoResp);}return ResultUtils.success(uploadFileInfoResp);}
前端
上文的代码中已经实现了秒传,在计算md5后最开始的地方调用后端秒传接口即可
const secondResult = await queryUploadFileApi(fileItem.md5)if (secondResult != null && secondResult.fileStatus == STATUS.upload_seconds.value) { //秒传fileItem.status = STATUS[secondResult.fileStatus].valuefileItem.uploadProgress = 100return;}
断点续传
断点续传就是用户已经上传过的分片不需要上传,直接返回即可。
我们的临时目录是以文件的MD5值命名的,分片文件的命名逻辑也一样,所以如果服务器中有该文件,并且它的文件大小跟要分片的文件大小一样,那就一定是上传成功的,逻辑分片上传的接口已经实现了,如下:
File newFile = new File(tempFileFolder.getPath() + "/" + chunkIndex);if(newFile.exists() && newFile.length() == multipartFile.getSize()){ //断点续传,不需要在重新上传uploadFileInfoResp.setFileStatus(UploadStatusEnum.UPLOADING.getStatus());return ResultUtils.success(uploadFileInfoResp);}
相关文章:
Springboot + vue3 实现大文件上传方案:秒传、断点续传、分片上传、前端异步上传
参考:https://juejin.cn/post/6870837414852886542#heading-9 一般计算大文件的md5都是前端来做,因为如果后端来做,那得等到上传成功后才能计算md5值,并且读取的时间也很长。 为了解决文件大传输慢的问题,前端可以通…...
渗透Vulnhub-DC-9靶机
本篇文章旨在为网络安全渗透测试行业靶机教学。通过阅读本文,读者将能够对渗透Vulnhub系列DC-6靶机有定的了解 一、信息收集阶段 DC-9靶场信息: DC-9靶场介绍: https://www.vulnhub.com/entry/dc-9,412/ DC-9靶场下载: https://download.vu…...
springboot477基于vue技术的农业设备租赁系统(论文+源码)_kaic
摘 要 使用旧方法对农业设备租赁系统的信息进行系统化管理已经不再让人们信赖了,把现在的网络信息技术运用在农业设备租赁系统的管理上面可以解决许多信息管理上面的难题,比如处理数据时间很长,数据存在错误不能及时纠正等问题。这次开发的农…...
CentOS常见命令
CentOS(Community ENTerprise Operating System)基于Red Hat Enterprise Linux(RHEL)源代码开发,是常用的Linux发行版之一。在CentOS系统中,有许多命令用于管理和操作系统,以下是一些CentOS系统…...
oracle 设置归档日志存放路径
oracle 设置归档日志存放路径 1、创建新目录 mkdir /archive chown -R oracle:oinstall /archive 注:条件允许的话,/archive 目录应独立挂载。1、便于监控目录使用率;2、避免和其它文件混淆,便于管理。 2、设置归档日志存放路…...
机器学习1-简单神经网络
相比传统的机器学习算法,深度学习做出了哪些改进呢?其实两者在理论结构上是一致的,即:模型假设、评价函数和优化算法,其根本差别在于假设的复杂度 构建简单神经网络(未训练): # 封装…...
C++的侵入式链表
非侵入式链表 非侵入式链表是一种链表数据结构,其中每个元素(节点)并不需要自己包含指向前后节点的指针。链表的结构和节点的存储是分开的,链表容器会单独管理这些指针。 常见的非侵入式链表节点可以由以下所示,即&a…...
MFC案例:图片文件转图标(ico)格式
本案例程序目的是将一般图像文件转换成图标格式(ico)。实现起来不是很复杂,这里为了介绍MFC的具体使用方法,在程序界面上分成几个功能块,包括:打开图像文件、选择ICON大小、转换、预览、保存等。相关具体步骤如下: 一、…...
【从零开始入门unity游戏开发之——unity篇02】unity6基础入门——软件下载安装、Unity Hub配置、安装unity编辑器、许可证管理
文章目录 一、软件下载安装1、Unity官网2、下载Unity Hub 二、修改Unity Hub配置1、设置Unity Hub中文语言2、修改默认存储目录 三、安装unity编辑器1、点击安装编辑器2、版本选择3、关于版本号4、安装模块选择5、等待下载完成自动安装即可6、追加unity和模块 四、许可证管理专…...
东子生物完成A轮战略融资,数字商品交易全新升级为数商时代
2024年11月23日,东子生物数字时代正式上线,标志着公司全面迈入“数商时代”,作为国内领先的生物科技企业,东子生物在数字化浪潮中精准布局,以创新科技推动产业升级,以全新的思维引领健康产业,兼…...
数据结构经典算法总复习(上卷)
第一章:数据结构导论 无重要考点,仅需了解时间复杂度。 第二章:线性表 1.获得线性表第i个元素 void GetElem_sq(SqList L, int i, ElemType &e) {if (i<1 || i>L.length) ErrorMsg("Invalid i value"); //注意错误监…...
电脑使用CDR时弹出错误“计算机丢失mfc140u.dll”是什么原因?“计算机丢失mfc140u.dll”要怎么解决?
电脑使用CDR时弹出“计算机丢失mfc140u.dll”错误:原因与解决方案 在日常电脑使用中,我们时常会遇到各种系统报错和文件丢失问题。特别是当我们使用某些特定软件,如CorelDRAW(简称CDR)时,可能会遇到“计算…...
oracle使用imp命令导入dmp文件
需求: 增量导入 tbl_servicelegalclause 表数据(dmp格式)。 导入思路:使用 dba 创建一个 临时库,先将 tbl_servicelegalclause.dmp(增量的数据) 文件导入到 临时库,然后确认临时库数…...
电脑出现 0x0000007f 蓝屏问题怎么办,参考以下方法尝试解决
电脑蓝屏是让许多用户头疼的问题,其中出现 “0x0000007f” 错误代码更是较为常见且棘手。了解其背后成因并掌握修复方法,能帮我们快速恢复电脑正常运行。 一、可能的硬件原因 内存问题 内存条长时间使用可能出现物理损坏,如金手指氧化、芯片…...
Logback日志框架中的继承机制详解
在Logback框架中,logger的继承机制是基于层级结构(hierarchical context)工作的。每个logger都被分配一个名称,这个名称可以看作是一个路径或目录结构,从而形成了一个逻辑上的树状结构。这种结构使得日志记录具有很强的…...
[Unity]【图形渲染】【游戏开发】Shader数学基础4-更多矢量运算
在计算机图形学和着色器编程中,矢量运算是核心的数学工具之一。矢量用于描述空间中的位置、方向、速度等各种物理量,并在图形变换、光照计算、纹理映射等方面起着至关重要的作用。本篇文章将详细讲解矢量和标量之间的乘法与除法、矢量的加法与减法、矢量的模与单位矢量、点积…...
node.js的异步工作之---回调函数与回调地狱
回调函数:在 Node.js 中,很多 API 都是异步的,通常通过回调函数来处理操作完成后的结果。这种回调模式虽然非常高效,但会导致代码逐渐变得难以维护,尤其是当有多个异步操作嵌套时(即回调地狱)。…...
tcp 的三次握手与四次挥手
问1: 请你说一下tcp的三次握手一次握手两次握手三次握手问: 为什么不四(更多)次握手? 问 2: 请说一下 tcp 的 4 次挥手一次挥手两次挥手问题:能不能等到数据传输完成再返回 ack? 三次挥手四次挥手问: 为什么要等两个最大报文存在时间? bg: tcp 是可靠的连接,如何保证 建立连…...
《三角洲行动》游戏运行时提示“缺失kernel32.dll”:问题解析与解决方案
《三角洲行动》游戏运行时提示“缺失kernel32.dll”:问题解析与解决方案 作为软件开发领域的一名从业者,我深知电脑游戏运行过程中可能遇到的各种挑战,尤其是文件丢失、文件损坏以及系统报错等问题。今天,我将以经典游戏《三角洲…...
Android——自定义按钮button
项目中经常高频使用按钮,要求:可设置颜色,有圆角且有按下效果的Button 一、自定义按钮button button的代码为 package com.fslihua.clickeffectimport android.annotation.SuppressLint import android.content.Context import android.gra…...
Pandas基础学习(1)
之前看的pandas的教材和课程里,内容参差不齐,实际使用很少的方法的内容有点多,导致很乱而且记不住那么多,这个帖子尽量用最少的文字,最精炼的语言来总结比较实用的方法,内容主要来源于《利用python进行数据…...
20241224在Ubuntu20.04.6下给X99平台上的M6000显卡安装驱动程序
20241224在Ubuntu20.04.6下给X99平台上的M6000显卡安装驱动程序 2024/12/24 16:18 下载驱动程序: https://www.nvidia.cn/drivers/lookup/ https://www.nvidia.cn/drivers/results/ https://www.nvidia.cn/drivers/details/237923/ https://www.nvidia.cn/drivers/l…...
批量多线程给TXT文档插入相关腾讯AI【高质量无水印无版权】原创图片
给蜘蛛访问的网站文章插入相关图片,可以带来以下好处: 1、提升用户体验:图片能够直观地展示文章内容,帮助用户更好地理解和消化信息。对于阅读者来说,图文并茂的内容往往更具吸引力,也能提高他们的阅读…...
保护模式基本概念
CPU 架构 RISC(Reduced Instruction Set Computer) 中文即"精简指令集计算机”。RISC构架的指令格式和长度通常是固定的(如ARM是32位的指令)、且指令和寻址方式少而简单、大多数指令在一个周期内就可以执行完毕 CISC&…...
mysql 查询优化之字段建立全文索引
最近在接手一些老项目时发现表设计存在问题导致查询较慢 例如一张旧表的设计: 模糊匹配某个关键字时,需要十几秒左右,而且表的数据量不多 都知道mysql8.0版本InnoDB引擎都支持全文索引了,因此可以在content建立全文索引,但全文索引对中文支持并不完善…...
redis和mysql的区别
Redis是一种非关系型数据库(NoSQL),将数据存储在缓存中,这虽然提高了运行效率,但是保存时间却很短。将数据存储在内存中,因此读写速度非常快,特别适合用于需要高速访问的场景,如缓存…...
【Laravel】接口的访问频率限制器
Laravel 接口的访问频率,你可以在 Laravel 中使用速率限制器(Rate Limiter)。以下是一个详细的步骤,展示如何为这个特定的 API 路由设置速率限制: 1. 配置 RouteServiceProvider 首先,确保在 App\Provide…...
LabVIEW水泵性能测试系统
在现代工业应用中,水泵作为一种广泛使用的流体输送设备,其性能的可靠性对整个生产系统的稳定运行至关重要。通过LabVIEW软件配合专业硬件设备,设计了一套水泵性能测试系统,实现对各类水泵的综合性能测试与分析,提升水泵…...
基于推理的目标检测 DetGPT
基于推理的目标检测 DetGPT flyfish detgpt.github.io 近年来,由于大型语言模型(LLMs)的发展,计算机视觉领域取得了重大进展。这些模型使人类与机器之间能够进行更有效、更复杂的交互,为模糊人类与机器智能界限的新技…...
【Java 基础】-- ArrayList 和 Linkedlist
目录 1. Java 中的 ArrayList 和 LinkedList 简介 ArrayList LinkedList 2. 相同数量级下的内存开销对比 ArrayList 的内存开销 LinkedList 的内存开销 3. 它们的速度对比 总结 1. Java 中的 ArrayList 和 LinkedList 简介 ArrayList 数据结构:基于动态数组…...
电脑不小心删除了msvcr120.dll文件怎么办?“缺失msvcr120.dll文件”要怎么解决?
一、文件丢失与损坏的常见原因及解决办法 1. 不小心删除系统文件 常见情况:有时在清理电脑垃圾文件时,可能会不小心删除一些重要的系统文件,如msvcr120.dll等。解决办法: 恢复文件:如果刚删除不久,可以尝…...
数据结构----链表头插中插尾插
一、链表的基本概念 链表是一种线性数据结构,它由一系列节点组成。每个节点包含两个主要部分: 数据域:用于存储数据元素,可以是任何类型的数据,如整数、字符、结构体等。指针域:用于存储下一个节点&#…...
【面试 - 遇到的问题】Vue 里 router-view 使用 key + 关闭页面后重新打开页面-获取的数据赋值到旧组件问题(钩子执行顺序)
目录 【1】问题描述【2】问题排查前 - 页面渲染、tag 页签渲染 逻辑梳理页面渲染【借用别人的描述】<router-view :key"key" />1. 不设置key 属性2. 设置 key 属性值为 $route.path/page/1 > /page/2/page?id1 > /page?id2, 3. 设置 key 属性值为 $rou…...
Dubbo简单总结
1、dubbo服务暴露的流程 ulr:protocol://username:password@host:port/path?param1=value1¶m2=value2 a、服务的暴露起始于 Spring IOC 容器刷新完毕之后,会根据配置参数组装成 URL, 然后根据 URL 的参数来进行本地或者远程调用。 b、会通过 proxyFactory.getInv…...
智慧社区电商系统:提升用户体验的界面设计
3.1可行性分析 开发者在进行开发系统之前,都需要进行可行性分析,保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该智慧社区电子商务系统所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识&…...
电感降额和选型规范
通常元器件有一个最佳降额范围,在此范围内,元器件工作应力的降低对其失效率的下降有显著的改善,设备的设计易于实现,且不必在设备的质量,体积,成本方面付出大的代价,应按设备可靠性要求…...
STM32HAL库中RTC闹钟设置时分秒,年月日
在STM32的HAL库中,RTC(实时时钟)模块提供了多种功能来管理时间和日期,包括设置闹钟。对于RTC闹钟功能,确实主要集中在时、分、秒的配置上,但年、月、日也可以通过RTC日期寄存器进行设置,并且可以…...
linux-19 根文件系统(一)
之前提到过,linux的目录是一个倒置的树,它通过层次性的方式来组织,管理整个系统的文件,而这本身实际上是通过文件系统。文件系统,大家记得文件系统是内核的主要功能之一, 它的主要目的就是实现本机上的某一…...
kotlin中泛型中in和out的区别
概念含义 in关键字(逆变) 在Kotlin泛型中,in关键字主要用于定义逆变(Contravariance)。它表示一个泛型类型参数可以是指定类型或者它的超类型。简单来说,就是对于类型A和B,如果A是B的子类型&…...
Spring学习(一)——Sping-XML
一、Spring的概述 (一)什么是Spring? Spring是针对bean对象的生命周期进行管理的轻量级容器。提供了功能强大IOC、AOP及Web MVC等功能。Spring框架主要由七部分组成:分别是 Spring Core、 Spring AOP、 Spring ORM、 Spring DAO、Spring Context、 Spring Web和 S…...
设计模式——桥接模式
文章目录 1. 定义2. 结构组成3. 桥接模式结构4. 示例代码5. 模式优势6. 总结 1. 定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的主要目的是将抽象部分与它的实现部分分离,使它们都可以独立地变化。 2. 结构组成 桥接模…...
python Enum类介绍及cls关键字用法详解
文章目录 Enum 类基本用法定制枚举枚举方法枚举类方法 cls 关键字类方法工厂方法总结 在 Python中, Enum 类和 cls 关键字有一些特定的用法和含义。下面我将详细解释它们的用法: Enum 类 Enum 类是Python标准库中的一个类,用于创建枚举&a…...
模型的多GPU并行训练,DDP
DDP全称是DistributedDataParallel, 在torch.nn.parallel里面。 今天总结一下用DDP进行多GPU并行训练的方法, 内容来自build gpt2加上自己的补充。 如果你有多块GPU,就可以充分利用它们。 DDP会创建多个process(进程,不是线程哦&…...
直流有刷电机多环控制(PID闭环死区和积分分离)
直流有刷电机多环控制 提高部分-第8讲 直流有刷电机多环控制实现(1)_哔哩哔哩_bilibili PID模型 外环的输出作为内环的输入,外环是最主要控制的效果,主要控制电机的位置。改变位置可以改变速度,改变速度是受电流控制。 实验环境 【 !】功能简介: 按下KEY1使能电机,按下…...
LabVIEW软件开发的未来趋势
LabVIEW软件开发的未来趋势可以从以下几个方面来分析: 1. 与AI和机器学习的深度结合 趋势:LabVIEW正在向集成AI和机器学习方向发展,尤其是在数据处理、预测性维护和自动化控制领域。 原因:AI技术的普及使得实验和工业场景中的…...
ChatGPT之父:奥尔特曼
奥尔特曼 阿尔特曼一般指萨姆奥尔特曼,他是OpenAI的联合创始人兼首席执行官,被称为“ChatGPT之父”.以下是其具体介绍: 个人经历 1985年4月22日出生于美国芝加哥,8岁学会编程,9岁拥有电脑,对信息技术和互联网产生兴趣.高中就读于约翰巴勒斯中学,后进入斯坦福大学主修计…...
MySQL8.0后的double write有什么变化
什么是double write? 一部分是内存中的double write buffer ,大小为2MB(16k一个页,一共128个页)。 第二部分是磁盘共享表空间的128个数据页,在对脏页进行落盘的时候,并不是直接进行落盘&#x…...
wsl ubuntu Unexpected error from cudaGetDeviceCount
wsl ubuntu Unexpected error from cudaGetDeviceCount 在这里插入图片描述 参考资料: Quad (4x) A6000 WSL2 CUDA Init Errors...
渐开线齿轮和摆线齿轮有什么区别?
摆线齿形与渐开线齿形的区别 虽然在比对这两种齿形,但有一个事情希望大家注意:渐开线齿轮只是摆线齿轮的一个特例。 (1)摆线齿形的压力角在啮合开始时最大,在齿节点减小到零,在啮合结束时再次增大到最大…...
状态图的理解和实践
状态图(State Diagram)是软件工程和系统设计中的一种重要工具,主要用于描述对象在其生命周期中的动态行为。通过状态图,我们可以清晰地看到对象所经历的状态序列、引起状态转移的事件(event)以及因状态转移…...