Java大文件上传、分片上传、多文件上传、断点续传、上传文件minio、分片上传minio等解决方案

一、上传说明

         文件上传花样百出,根据不同场景使用不同方案进行实现尤为必要。通常开发过程中,文件较小,直接将文件转化为字节流上传到服务器,但是文件较大时,用普通的方法上传,显然效果不是很好,当文件上传一半中断再次上传时,发现需要重新开始,这种体验不是很爽,下面介绍几种好一点儿的上传方式。

        这里讲讲如何在Spring boot 编写上传代码,如有问题可以在下留言,我并在文章末尾附上Java上传源码供大家下载。

分片上传

分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分
隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再
由服务端对所有上传的文件进行汇总整合成原始的文件。

断点续传

断点续传是在下载/上传时,将下载/上传任务(一个文件或一个压缩
包)人为的划分为几个部分,每一个部分采用一个线程进行上传/下载,
如果碰到网络故障,可以从已经上传/下载的部分开始继续上传/下载
未完成的部分,而没有必要从头开始上传/下载。

二、Redis启动安装

Redis安装包分为 Windows 版和 Linux 版:
Windows版下载地址:https://github.com/microsoftarchive/redis/releases
Linux版下载地址: https://download.redis.io/releases/
我当前使用的Windows版本:

三、minio下载启动

windows版本可以参考我之前的文档:window10安装minio_minio windows安装-CSDN博客

启动会提示:

以上是密码设置问题需要修改如下:

set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678

启动成功后会输出相应地址

四、上传后端Java代码

        后端采用Spring boot项目结构,主要代码如下:

  /*** 单文件上传* 直接将传入的文件通过io流形式直接写入(服务器)指定路径下** @param file 上传的文件* @return*/@Overridepublic ResultEntity<Boolean> singleFileUpload(MultipartFile file) {//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";File dir = new File(filePath);if (!dir.exists()) dir.mkdir();if (file == null) {return ResultEntity.error(false, "上传文件为空!");}InputStream fileInputStream = null;FileOutputStream fileOutputStream = null;try {String filename = file.getOriginalFilename();fileOutputStream = new FileOutputStream(filePath + filename);fileInputStream = file.getInputStream();byte[] buf = new byte[1024 * 8];int length;while ((length = fileInputStream.read(buf)) != -1) {//读取fis文件输入字节流里面的数据fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去}log.info("单文件上传完成!文件路径:{},文件名:{},文件大小:{}", filePath, filename, file.getSize());return ResultEntity.success(true, "单文件上传完成!");} catch (IOException e) {return ResultEntity.error(true, "单文件上传失败!");} finally {try {if (fileOutputStream != null) {fileOutputStream.close();fileOutputStream.flush();}if (fileInputStream != null) {fileInputStream.close();}} catch (Exception e) {e.printStackTrace();}}}/*** 多文件上传* 直接将传入的多个文件通过io流形式直接写入(服务器)指定路径下* 写入指定路径下是通过多线程进行文件写入的,文件写入线程执行功能就和上面单文件写入是一样的** @param files 上传的所有文件* @return*/@Overridepublic ResultEntity<Boolean> multipleFileUpload(MultipartFile[] files) {//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";File dir = new File(filePath);if (!dir.exists()) dir.mkdir();if (files.length == 0) {return ResultEntity.error(false, "上传文件为空!");}ArrayList<String> uploadFiles = new ArrayList<>();try {ArrayList<Future<String>> futures = new ArrayList<>();//使用多线程来完成对每个文件的写入for (MultipartFile file : files) {futures.add(partMergeTask.submit(new MultipleFileTaskExecutor(filePath, file)));}//这里主要用于监听各个文件写入线程是否执行结束int count = 0;while (count != futures.size()) {for (Future<String> future : futures) {if (future.isDone()) {uploadFiles.add(future.get());count++;}}Thread.sleep(1);}log.info("多文件上传完成!文件路径:{},文件信息:{}", filePath, uploadFiles);return ResultEntity.success(true, "多文件上传完成!");} catch (Exception e) {log.error("多文件分片上传失败!", e);return ResultEntity.error(true, "多文件上传失败!");}}/*** 单文件分片上传* 直接将传入的文件分片通过io流形式写入(服务器)指定临时路径下* 然后判断是否分片都上传完成,如果所有分片都上传完成的话,就把临时路径下的分片文件通过流形式读入合并并从新写入到(服务器)指定文件路径下* 最后删除临时文件和临时文件夹,临时文件夹是通过文件的uuid进行命名的** @param filePart  分片文件* @param partIndex 当前分片值* @param partNum   所有分片数* @param fileName  当前文件名称* @param fileUid   当前文件uuid* @return*/@Overridepublic ResultEntity<Boolean> singleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径String tempPath = filePath + "temp\\" + fileUid;//临时文件存放路径File dir = new File(tempPath);if (!dir.exists()) dir.mkdirs();//生成一个临时文件名String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";try {//将分片存储到临时文件夹中filePart.transferTo(new File(tempFileNamePath));File tempDir = new File(tempPath);File[] tempFiles = tempDir.listFiles();one:if (partNum.equals(Objects.requireNonNull(tempFiles).length)) {//需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成if (isMergePart.get(fileUid) != null) {break one;}isMergePart.put(fileUid, tempFiles.length);System.out.println("所有分片上传完成,预计总分片:" + partNum + "; 实际总分片:" + tempFiles.length);FileOutputStream fileOutputStream = new FileOutputStream(filePath + fileName);//这里如果分片很多的情况下,可以采用多线程来执行for (int i = 0; i < partNum; i++) {//读取分片数据,进行分片合并FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + fileName + "_" + i + ".part");byte[] buf = new byte[1024 * 8];//8MBint length;while ((length = fileInputStream.read(buf)) != -1) {//读取fis文件输入字节流里面的数据fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去}fileInputStream.close();}fileOutputStream.flush();fileOutputStream.close();// 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流,可能导致删除失败for (int i = 0; i < partNum; i++) {boolean delete = new File(tempPath + "\\" + fileName + "_" + i + ".part").delete();File file = new File(tempPath + "\\" + fileName + "_" + i + ".part");}//在删除对应的临时文件夹if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {tempDir.delete();}isMergePart.remove(fileUid);}} catch (Exception e) {log.error("单文件分片上传失败!", e);return ResultEntity.error(false, "单文件分片上传失败");}//通过返回成功的分片值,来验证分片是否有丢失return ResultEntity.success(true, partIndex.toString());}/*** 多文件分片上传* 先将所有文件分片读入到(服务器)指定临时路径下,每个文件的分片文件的临时文件夹都是已文件的uuid进行命名的* 然后判断对已经上传所有分片的文件进行合并,此处是通过多线程对每一个文件的分片文件进行合并的* 最后对已经合并完成的分片临时文件和文件夹进行删除** @param filePart  分片文件* @param partIndex 当前分片值* @param partNum   总分片数* @param fileName  当前文件名称* @param fileUid   当前文件uuid* @return*/@Overridepublic ResultEntity<String> multipleFilePartUpload(MultipartFile filePart, Integer partIndex, Integer partNum, String fileName, String fileUid) {//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径String tempPath = filePath + "temp\\" + fileUid;//临时文件存放路径File dir = new File(tempPath);if (!dir.exists()) dir.mkdirs();//生成一个临时文件名String tempFileNamePath = tempPath + "\\" + fileName + "_" + partIndex + ".part";try {filePart.transferTo(new File(tempFileNamePath));File tempDir = new File(tempPath);File[] tempFiles = tempDir.listFiles();//如果临时文件夹中分片数量和实际分片数量一致的时候,就需要进行分片合并one:if (partNum.equals(tempFiles.length)) {//需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成if (isMergePart.get(fileUid) != null) {break one;}isMergePart.put(fileUid, tempFiles.length);System.out.println(fileName + ":所有分片上传完成,预计总分片:" + partNum + "; 实际总分片:" + tempFiles.length);//使用多线程来完成对每个文件的合并Future<Integer> submit = partMergeTask.submit(new PartMergeTaskExecutor(filePath, tempPath, fileName, partNum));System.out.println("上传文件名:" + fileName + "; 总大小:" + submit.get());isMergePart.remove(fileUid);}} catch (Exception e) {log.error("{}:多文件分片上传失败!", fileName, e);return ResultEntity.error("", "多文件分片上传失败");}//通过返回成功的分片值,来验证分片是否有丢失return ResultEntity.success(partIndex.toString(), fileUid);}/*** 多文件(分片)秒传* 通过对比已有的文件分片md5值和需要上传文件分片的MD5值,* 在文件分片合并的时候,对已有的文件进行地址索引,对没有的文件进行临时文件写入* 最后合并的时候根据不同的文件分片进行文件读取写入** @param filePart  上传没有的分片文件* @param fileInfo  当前分片文件相关信息* @param fileOther 已存在文件分片相关信息* @return*/@Overridepublic ResultEntity<String> multipleFilePartFlashUpload(MultipartFile filePart, String fileInfo, String fileOther) {DiskFileIndexVo upFileInfo = JSONObject.parseObject(fileInfo, DiskFileIndexVo.class);List<DiskFileIndexVo> notUpFileInfoList = JSON.parseArray(fileOther, DiskFileIndexVo.class);//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径//正常情况下,这个临时文件也应该放入(服务器)非临时文件夹中,这样方便下次其他文件上传查找是否曾经上传过类似的//当前demo是单独存放在临时文件夹中,文件合并完成之后直接删除的String tempPath = filePath + "temp\\" + upFileInfo.getFileUid();//临时文件存放路径File dir = new File(tempPath);if (!dir.exists()) dir.mkdirs();//生成一个临时文件名String tempFileNamePath = tempPath + "\\" + upFileInfo.getFileName() + "_" + upFileInfo.getPartIndex() + ".part";try {filePart.transferTo(new File(tempFileNamePath));File tempDir = new File(tempPath);File[] tempFiles = tempDir.listFiles();notUpFileInfoList = notUpFileInfoList.stream().filter(e ->upFileInfo.getFileUid().equals(e.getFileUid())).collect(Collectors.toList());//如果临时文件夹中分片数量和实际分片数量一致的时候,就需要进行分片合并one:if ((upFileInfo.getPartNum() - notUpFileInfoList.size()) == tempFiles.length) {//需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成if (isMergePart.get(upFileInfo.getFileUid()) != null) {break one;}isMergePart.put(upFileInfo.getFileUid(), tempFiles.length);System.out.println(upFileInfo.getFileName() + ":所有分片上传完成,预计总分片:" + upFileInfo.getPartNum()+ "; 实际总分片:" + tempFiles.length + "; 已存在分片数:" + notUpFileInfoList.size());//使用多线程来完成对每个文件的合并Future<Integer> submit = partMergeTask.submit(new PartMergeFlashTaskExecutor(filePath, upFileInfo, notUpFileInfoList));isMergePart.remove(upFileInfo.getFileUid());}} catch (Exception e) {log.error("{}:多文件(分片)秒传失败!", upFileInfo.getFileName(), e);return ResultEntity.error("", "多文件(分片)秒传失败!");}//通过返回成功的分片值,来验证分片是否有丢失return ResultEntity.success(upFileInfo.getPartIndex().toString(), upFileInfo.getFileUid());}/*** 根据传入需要上传的文件片段的md5值来对比服务器中的文件的md5值,将已有对应的md5值的文件过滤出来,* 通知前端或者自行出来这些文件,即为不需要上传的文件分片,并将已有的文件分片地址索引返回给前端进行出来** @param upLoadFileListMd5 原本需要上传文件的索引分片信息* @return*/@Overridepublic ResultEntity<List<DiskFileIndexVo>> checkDiskFile(List<DiskFileIndexVo> upLoadFileListMd5) {List<DiskFileIndexVo> notUploadFile;try {//后端服务器已经存在的分片md5值集合List<DiskFileIndexVo> diskFileMd5IndexList = diskFileIndexVos;notUploadFile = upLoadFileListMd5.stream().filter(uf -> diskFileMd5IndexList.stream().anyMatch(df -> {if (df.getFileMd5().equals(uf.getFileMd5())) {uf.setFileIndex(df.getFileName());//不需要上传文件的地址索引return true;}return false;})).collect(Collectors.toList());log.info("过滤出不需要上传的文件分片:{}", notUploadFile);} catch (Exception e) {log.error("上传文件检测异常!", e);return ResultEntity.error("上传文件检测异常!");}return ResultEntity.success(notUploadFile);}/*** 根据文件uuid(md5生成的)来判断此文件在服务器中是否未上传完整,* 如果没上传完整,则返回相关上传进度等信息** @param pointFileIndexVo* @return*/@Overridepublic ResultEntity<PointFileIndexVo> checkUploadFileIndex(PointFileIndexVo pointFileIndexVo) {try {List<String> list = uploadProgress.get(pointFileIndexVo.getFileMd5());if (list == null) list = new ArrayList<>();pointFileIndexVo.setParts(list);System.out.println("已上传部分:" + list);return ResultEntity.success(pointFileIndexVo);} catch (Exception e) {log.error("上传文件检测异常!", e);return ResultEntity.error("上传文件检测异常!");}}/*** 单文件(分片)断点上传** @param filePart 需要上传的分片文件* @param fileInfo 当前需要上传的分片文件信息,如uuid,文件名,文件总分片数量等* @return*/@Overridepublic ResultEntity<String> singleFilePartPointUpload(MultipartFile filePart, String fileInfo) {PointFileIndexVo pointFileIndexVo = JSONObject.parseObject(fileInfo, PointFileIndexVo.class);//实际情况下,这些路径都应该是服务器上面存储文件的路径String filePath = System.getProperty("user.dir") + "\\file\\";//文件存放路径String tempPath = filePath + "temp\\" + pointFileIndexVo.getFileMd5();//临时文件存放路径File dir = new File(tempPath);if (!dir.exists()) dir.mkdirs();//生成一个临时文件名String tempFileNamePath = tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + pointFileIndexVo.getPartIndex() + ".part";try {//将分片存储到临时文件夹中filePart.transferTo(new File(tempFileNamePath));List<String> partIndex = uploadProgress.get(pointFileIndexVo.getFileMd5());if (Objects.isNull(partIndex)) {partIndex = new ArrayList<>();}partIndex.add(pointFileIndexVo.getPartIndex().toString());uploadProgress.put(pointFileIndexVo.getFileMd5(), partIndex);File tempDir = new File(tempPath);File[] tempFiles = tempDir.listFiles();one:if (pointFileIndexVo.getPartNum().equals(Objects.requireNonNull(tempFiles).length)) {//需要校验一下,表示已有异步程序正在合并了;如果是分布式这个校验可以加入redis的分布式锁来完成if (isMergePart.get(pointFileIndexVo.getFileMd5()) != null) {break one;}isMergePart.put(pointFileIndexVo.getFileMd5(), tempFiles.length);System.out.println("所有分片上传完成,预计总分片:" + pointFileIndexVo.getPartNum() + "; 实际总分片:" + tempFiles.length);//读取分片数据,进行分片合并FileOutputStream fileOutputStream = new FileOutputStream(filePath + pointFileIndexVo.getFileName());//这里如果分片很多的情况下,可以采用多线程来执行for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {FileInputStream fileInputStream = new FileInputStream(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");byte[] buf = new byte[1024 * 8];//8MBint length;while ((length = fileInputStream.read(buf)) != -1) {//读取fis文件输入字节流里面的数据fileOutputStream.write(buf, 0, length);//通过fos文件输出字节流写出去}fileInputStream.close();}fileOutputStream.flush();fileOutputStream.close();// 删除临时文件夹里面的分片文件 如果使用流操作且没有关闭输入流,可能导致删除失败for (int i = 0; i < pointFileIndexVo.getPartNum(); i++) {boolean delete = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part").delete();File file = new File(tempPath + "\\" + pointFileIndexVo.getFileName() + "_" + i + ".part");}//在删除对应的临时文件夹if (Objects.requireNonNull(tempDir.listFiles()).length == 0) {tempDir.delete();}isMergePart.remove(pointFileIndexVo.getFileMd5());uploadProgress.remove(pointFileIndexVo.getFileMd5());}} catch (Exception e) {log.error("单文件分片上传失败!", e);return ResultEntity.error(pointFileIndexVo.getFileMd5(), "单文件分片上传失败");}//通过返回成功的分片值,来验证分片是否有丢失return ResultEntity.success(pointFileIndexVo.getFileMd5(), pointFileIndexVo.getPartIndex().toString());}/*** 获取(服务器)指定文件存储路径下所有文件MD5值* 实际情况下,每一个文件的md5值都是单独保存在数据库或者其他存储机制中的,* 不需要每次都去读取文件然后获取md5值,这样多次io读取很耗性能** @return* @throws Exception*/@Beanprivate List<DiskFileIndexVo> getDiskFileMd5Index() throws Exception {String filePath = System.getProperty("user.dir") + "\\file\\part\\";File saveFileDir = new File(filePath);if (!saveFileDir.exists()) saveFileDir.mkdirs();List<DiskFileIndexVo> diskFileIndexVoList = new ArrayList<>();File[] listFiles = saveFileDir.listFiles();if (listFiles == null) return diskFileIndexVoList;for (File listFile : listFiles) {String fileName = listFile.getName();FileInputStream fileInputStream = new FileInputStream(filePath + fileName);String md5DigestAsHex = DigestUtils.md5DigestAsHex(fileInputStream);DiskFileIndexVo diskFileIndexVo = new DiskFileIndexVo();diskFileIndexVo.setFileName(fileName);diskFileIndexVo.setFileMd5(md5DigestAsHex);diskFileIndexVoList.add(diskFileIndexVo);fileInputStream.close();}diskFileIndexVos = diskFileIndexVoList;log.info("服务器磁盘所有文件 {}", diskFileIndexVoList);return diskFileIndexVoList;}

 代码结构图:

五、前端代码

   整体的过程如下

前端将文件按照百分比进行计算,每次上传文件的百分之一(文件分片),给文件分片做上序号及文件uuid,然后在循环里面对文件片段上传的时候在将当前分片值一起传给后端。
后端将前端每次上传的文件,放入到缓存目录;
前端将全部的文件内容都上传完毕后,发送一个合并请求;
后端合并分片的之后对文件进行命名保存;
后端保存临时分片的时候命名索引,方便合并的时候按照分片索引进行合并;

vue模板代码:

      <!-- 单文件分片上传 --><div class="fileUploadStyle"><h3>单文件分片上传</h3><el-upload ref="upload" name="files" action="#" :on-change="selectSinglePartFile":on-remove="removeSingleFilePart" :file-list="singleFilePart.fileList" :auto-upload="false"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button style="margin-left: 10px;" size="small" type="success"@click="singleFilePartUpload">点击进行单文件分片上传</el-button><div slot="tip" class="el-upload__tip">主要用于测试单文件分片上传</div></el-upload><el-progress :text-inside="true" class="progress" :stroke-width="26" :percentage="singlePartFileProgress" /></div><!-- 多文件分片上传 --><div class="fileUploadStyle"><h3>多文件分片上传</h3><el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFile":on-remove="removeMultiplePartFile" :file-list="multipleFilePart.fileList" :auto-upload="false"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button style="margin-left: 10px;" size="small" type="success"@click="multipleFilePartUpload">点击进行多文件分片上传</el-button><div slot="tip" class="el-upload__tip">主要用于测试多文件分片上传</div></el-upload></div><!-- 多文件(分片)秒传 --><div class="fileUploadStyle"><h3>多文件(分片MD5值)秒传</h3><el-upload ref="upload" name="files" action="#" :on-change="selectMultiplePartFlashFile":on-remove="removeMultiplePartFlashFile" :file-list="multipleFilePartFlash.fileList" :auto-upload="false"><el-button slot="trigger" size="small" type="primary">选取文件</el-button><el-button style="margin-left: 10px;" size="small" type="success"@click="multipleFilePartFlashUpload">点击进行文件秒传</el-button><div slot="tip" class="el-upload__tip">主要用于测试多文件(分片MD5值)秒传</div></el-upload></div>

js属性定义:

上传部分代码:

minio分片上传:

上传样式:

六、功能演示及源码

部分演示图: 这里就以上传minio为例,测试上传minio以分片方式上传

以8M进行分切 28M刚好分了四个区,我们使用redis客户工具查看

最后成功上传到minio中

而且看到上传文件大小为:28M

       文件上传代码其实功能也简单也很明确,先将一个大文件分成n个小文件,然后给后端检测这些分片是否曾经上传中断过,即对这些分片进行过滤出来,并将过滤出对应的分片定位值结果返回给前端处理出不需要上传的分片和需要上传的文件分片,这里主要还是区分到确定是这个文件的分区文件。

        这里,为了方便大家直接能够使用Java源码,本文所有都采用Spring boot框架模式,另外使用了第三方插件,如果大家使用中没有使用到minio可以不需要引入并把相关代码移除即可,代码使用了redis作为分区数量缓存,相对于Java内存更稳定些。

demo源码下载gitee地址(代码包含Java后端工程和vue2前端工程):java-file-upload-demo: java 多文件上传、多文件分片上传、多文件秒传、minio分片上传等功能

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

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

相关文章

【Unity脚本】修改游戏对象的活动状态

【知识链】Unity -> Unity脚本 -> 游戏对象 -> 活动状态【摘要】本文介绍了如何通过编辑器和脚本来访问游戏对象的活动状态&#xff0c;并给出具体的场景示例。 文章目录 第一章 引言第二章 在编辑器中设置活动状态2.1. 在编辑器中设置活动状态2.1.1. 停用游戏对象2.…

文件IO(三)

文件IO&#xff08;三&#xff09; 左移右移Linux的man 手册文件IO打开文件操作文件关闭文件 caps lock开灯关灯读取按键文件IO操作目录文件打开目录文件操作目录文件 库动态库和静态库的优缺点创建静态库创建动态库 按下右ctrl键 亮灭灯 左移右移 Linux的man 手册 文件IO 打开…

FJSP:常春藤算法(Ivy algorithm,LVYA)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

详细介绍 FJSP&#xff1a;常春藤算法&#xff08;Ivy algorithm&#xff0c;LVYA&#xff09;求解柔性作业车间调度问题&#xff08;FJSP&#xff09;&#xff0c;提供MATLAB代码-CSDN博客 完整MATLAB代码 FJSP&#xff1a;常春藤算法&#xff08;Ivy algorithm&#xff0c;…

图形学初识--多边形剪裁算法

文章目录 前言正文为什么需要多边形剪裁算法&#xff1f;前置知识二维直线直线方程&#xff1a;距离本质&#xff1a;点和直线距离关系&#xff1a; 三维平面平面方程距离本质&#xff1a;点和直线距离关系&#xff1a; Suntherland hodgman算法基本介绍基本思想二维举例问题描…

最小时间差

首先可以想到&#xff0c;可以计算出任意两个时间之间的差值&#xff0c;然后比较出最小的&#xff0c;不过这种蛮力方法时间复杂度是O(n^2)。而先将时间列表排序&#xff0c;再计算相邻两个时间的差值&#xff0c;就只需要计算n个差值&#xff0c;而排序阶段时间复杂度通常为O…

C语言实现贪吃蛇小游戏(控制台)

本篇主要内容是使用C语言在Windows环境的控制台中模拟实现经典小游戏贪吃蛇。 一、准备工作 我们要实现的基本功能有&#xff1a; 地图绘制蛇吃食物的功能&#xff08;上、下、左、右方向键控制蛇的动作&#xff09;蛇撞墙死亡蛇撞自身死亡计算得分蛇身加速、减速暂停游戏 …

9-Django项目--验证码操作

目录 templates/login/login.html utils/code.py views/login.py 验证码 生成验证码 code.py 应用验证码 views.py login.html templates/login/login.html {% load static %} <!DOCTYPE html> <html lang"en"> <head><meta charset&q…

PID算法入门

文章目录 122.12.22.3 344.14.24.3 1 e(t) 是偏差 实 和 目u(t) 是运算结果 2 层层叠加 得出完整的离散公式 2.1 kp 越大 系统偏差 减小的越快kp大的时候 会出现过冲现象&#xff1f; 0.5 那个会快他解释过冲 &#xff1a; 0.2的 5分钟正好到了 那0.5的五分钟 升的就比20多 就…

④单细胞学习-cellchat细胞间通讯

目录 1&#xff0c;原理基础 流程 受体配体概念 方法比较 计算原理 2&#xff0c;数据 3&#xff0c;代码运行 1&#xff0c;原理基础 原文学习Inference and analysis of cell-cell communication using CellChat - PMC (nih.gov) GitHub - sqjin/CellChat: R toolk…

在 JavaScript 中实现数据加密与解密:Web Cryptography API 与 CryptoJS详解

在 JavaScript 中&#xff0c;可以使用 Web Cryptography API 或第三方库如 crypto-js 来实现加密和解密。本文将介绍如何使用这两种方法在客户端进行数据的加密和解密。 使用 Web Cryptography API Web Cryptography API 是现代浏览器提供的一个强大、原生的加密 API。它允许…

关于留痕的使用常见的问题

1. 登录微信 登录要导出数据的微信&#xff08;不支持微信多开&#xff0c;不支持部分老版本微信&#xff09; 相关信息 想把手机端的微信聊天记录转移到电脑上可以使用微信自带的聊天记录迁移功能 操作步骤&#xff1a; 安卓&#xff1a; 手机微信->我->设置->聊…

[深度学习]使用python部署yolov10的onnx模型

测试环境&#xff1a; onnxruntime1.15.1 opencv-python4.8.0.76 部分实现代码&#xff1a; parser argparse.ArgumentParser()parser.add_argument("--model", typestr, default"yolov10n.onnx", help"Input your ONNX model.")parser.add_arg…

电子电器架构 --- 什么是域控制器?

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

美颜相机与美图秀秀的非会员图片保存技巧畅享专业级图像处理探索

美颜相机与美图秀秀的非会员图片保存技巧畅享专业级图像处理探索 今日对美颜相机和美图秀秀的深入使用中&#xff0c;我遇到了一些功能限制&#xff0c;特别是在尝试保存特定处理后的图片时&#xff0c;发现通常需要开通VIP会员才能享受完整服务。作为一名热衷于技术探索的爱好…

【数据结构】二叉树的层序遍历~动画超详解

目录 1 什么是层序遍历2 二叉树层序遍历的基本思路3 二叉树层序遍历的实现 1 什么是层序遍历 我们从字面意思就明白,所谓层序,就是一层一层按顺序去遍历一个二叉树,这和我们之前了解的按前中后序遍历方式完全不同 比方说这颗二叉树: 前序遍历: 层序遍历: 2 二叉树层序遍历的…

Android 使用kotlin Retrofit2 + Dagger2完成网络请求跟依赖注入组合使用

文章目录 &#xff08;一&#xff09;引入依赖&#xff08;二&#xff09;基本概念Dagger中的基本概念&#xff1a;Retrofit介绍 &#xff08;三&#xff09;Dagger2 Module 和 Provides 和 Component Inject&#xff08;四&#xff09;Retrofit2 创建数据类Bean跟Service服务&…

3. MySQL 数据表的基本操作

文章目录 【 1. MySQL 创建数据表 】【 2. MySQL 查看表 】2.1 查看表的属性DESCRIBE/DESC 以表格的形式展示表属性SHOW CREATE TABLE 以SQL语句的形式展示表属性 2.2 查看表的内容 【 3. MySQL 修改数据表结构 】3.1 修改表名3.2 修改表字符集3.3 添加字段在末尾添加字段在开头…

LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks

更多精彩内容&#xff0c;请关注微信公众号&#xff1a;NLP分享汇 原文链接&#xff1a;LLMs Can’t Plan, But Can Help Planning in LLM-Modulo Frameworks 你是怎么理解LLM的规划和推理能力呢&#xff0c;来自亚利桑那州立大学最近的一篇论文&#xff0c;对LLM的规划、推理…

ios 新安装app收不到fcm推送

&#x1f3c6;本文收录于「Bug调优」专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&&…

拼图游戏完整思路(全代码演示)

主界面 小练习1&#xff1a; 一、三个界面的设置1&#xff1a;创建窗体 1、将三个主界面分开为三个类&#xff0c;每个类都去继承JFrame这个类&#xff0c;使得每个类都可以使用创建页面功能 2、对每个类进行空参构造&#xff0c;在空参构造里面进行窗体属性的赋值 3、创建一个…