Springboot 文件传输优化

        Spring Boot通过MultipartFile接口简化了文件上传的处理。在Spring MVC中,你可以使用@RequestParam注解与MultipartFile一起工作来接收上传的文件。Spring Boot默认配置了文件上传的大小限制,但是你可以通过application.properties进行自定义配置。

# 单个文件大小限制
spring.servlet.multipart.max-file-size=100MB

# 请求中所有文件的总大小限制
spring.servlet.multipart.max-request-size=150MB

1、Java IO vs NIO

        Java IO是针对流式数据设计的,每次只能操作一个字节的数据。这让它在处理大文件或进行频繁的文件操作时,性能会相对较低。相反,Java NIO设计用来处理块或缓冲区的数据,一个缓冲区可以包含多个字节的数据,因此在处理大文件或频繁的文件操作时,性能可能更好。

2、文件传输方法性能对比

a、Spring 文件上传提供的方法 MultipartFile#transferTo()

@PostMapping("/upload")
public void upload(@RequestParam("file") MultipartFile file) {File dest = new File("path/to/destination");file.transferTo(dest.toPath());
}

方法内部使用了java.nio.file.Files#copy(java.io.InputStream, java.nio.file.Path, java.nio.file.CopyOption...)来实现文件的读写操作,这是NIO API的一部分。然而,这种方式仍然是在IO级别进行操作,并没有使用到NIO中的通道(Channel)和缓冲区(Buffer)机制,因此性能表现并不优秀。

b、FileChannel#transferTo

        文件通道(FileChannel)是一种可以从文件中读取、写入的通道。一般来说,文件通道的性能比Java IO的流更好。transferTo()方法的存在就是为了提高性能。 

@PostMapping("/upload")
public void uploadNio(@RequestParam("file") MultipartFile file) {try (FileOutputStream fos = new FileOutputStream("path/to/destination");FileChannel outChannel = fos.getChannel()) {FileChannel inChannel = ((FileInputStream) file.getInputStream()).getChannel();inChannel.transferTo(0, inChannel.size(), outChannel);}
}

这个方法使用了Java NIO库中的FileChannel,并且使用到了操作系统的“零拷贝”特性。“零拷贝”可以减少用户空间和内核空间之间的数据拷贝,因此在处理大文件时,它的性能会比org.springframework.web.multipart.MultipartFile#transferTo(java.nio.file.Path)更好。

c、完整代码及耗时对比如下

@RequestMapping(value = "/upload", method = RequestMethod.POST,consumes = "multipart/form-data")public CommResp handleFileUpload(@RequestParam("file") MultipartFile file,@RequestParam String erp) {Preconditions.checkArgument(StringUtils.isNotBlank(erp), "上传人erp不存在");Preconditions.checkArgument(file != null && !file.isEmpty(), "文件不能为空");//获取原文件名String originalFilename = file.getOriginalFilename();Preconditions.checkArgument(StringUtils.isNotBlank(originalFilename),"文件名称不能为空");//获取文件后缀String suffixName = originalFilename.substring(originalFilename.lastIndexOf("."));// 文件重命名String storagePath = UPLOAD_PATH + "/" + erp + "_" + System.currentTimeMillis() + suffixName;new Thread(() -> {// 实现方式1long startTime = System.currentTimeMillis();try(//创建文件流FileInputStream fis = (FileInputStream)file.getInputStream();FileOutputStream fos = new FileOutputStream(UPLOAD_PATH + "/" + erp + "_1_" + System.currentTimeMillis() + suffixName);//创建通道(通道间传输)FileChannel inChannel = fis.getChannel();FileChannel outChannel = fos.getChannel()){//使用零拷贝上传inChannel.transferTo(0,inChannel.size(),outChannel);}catch (IOException e){log.error("文件上传失败",e);}log.info("elapsed time1: {}", System.currentTimeMillis() - startTime);}).start();// 实现方式2long startTime = System.currentTimeMillis();try{Path targetPath = Paths.get(storagePath);file.transferTo(targetPath);}catch (IOException e){log.error("文件上传失败",e);return CommResp.failure("文件上传失败");}log.info("elapsed time2: {}", System.currentTimeMillis() - startTime);return CommResp.success(new UploadResultBO(originalFilename.replace(suffixName, ""),storagePath));}

PublishController        :  elapsed time1: 402
PublishController        :  elapsed time2: 1178

d、进一步优化传输性能思路

  • 多线程分块传输

        将大文件分割成多个部分,为每个部分分配一个单独的线程进行读写操作。这样可以充分利用多核CPU的计算能力,提高数据处理的并行度。

代码实现:

@PostMapping("/upload")public void uploadNioMultiThread(@RequestParam("file") MultipartFile file) {try (RandomAccessFile raf = new RandomAccessFile("path/to/destination", "rw");FileChannel outChannel = raf.getChannel()) {FileChannel inChannel = ((FileInputStream) file.getInputStream()).getChannel();long size = inChannel.size();long blockSize = size / THREAD_NUM;CountDownLatch latch = new CountDownLatch(THREAD_NUM);for (int i = 0; i < THREAD_NUM; i++) {long start = i * blockSize;long end = (i == THREAD_NUM - 1) ? size : start + blockSize;executorService.submit(() -> {try (FileChannel channel = (FileChannel) Channels.newChannel(file.getInputStream())) {channel.position(start);outChannel.position(start);channel.transferTo(start, end - start, outChannel);} catch (IOException e) {e.printStackTrace();} finally {latch.countDown();}});}latch.await();} catch (Exception e) {e.printStackTrace();}}
  • 使用内存映射文件

        NIO提供了内存映射文件的支持,可以通过映射到内存的方式来直接操作文件,省去了内核空间和用户空间之间数据复制的开销。

代码实现:

/*** 1、开启输入文件Channel* 2、计算文件大小* 3、循环上传文件内容,每次处理10mb大小的数据块* 4、使用FileChannel.MapMode.READ_ONLY模式,以只读的方式,将输入文件的内容映射到内存中* 5、使用内存映射,将生成的ByteBuffer写入到输出文件Channel中* 6、修改当前位置,以便处理下一个数据块* @param file*/@PostMapping("/upload")public void uploadFileWithMemoryMapped(@RequestParam("file") MultipartFile file) {try (RandomAccessFile raf = new RandomAccessFile("path/to/destination", "rw");FileChannel outChannel = raf.getChannel()) {FileChannel inChannel = ((FileInputStream) file.getInputStream()).getChannel();long fileSize = inChannel.size();long pos = 0L;/* Map the file into memory, and upload it by parts. */while (pos < fileSize) {long limit = Math.min(10485760L, fileSize - pos);  // map 10MB at a timeMappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, pos, limit);outChannel.write(inMappedBuf);  // This writes data to the filepos += limit;}} catch (IOException e) {// handle exception}}

在处理大型文件传输时,可以考虑同时实现内存映射文件和多线程分片传输,以提高大型文件处理的程序性能。

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

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

相关文章

短剧看剧系统,当前互联网热门项目工具系统模板。

目录 揭秘爆款神器&#xff1a;短剧看剧系统&#xff0c;让你的内容火遍全网&#xff01; 一、短剧看剧系统&#xff1a;一站式解决方案 二、灵活定价&#xff0c;实现收益最大化 三、高效管理&#xff0c;团队协作更轻松 四、数据驱动&#xff0c;精准把握市场动态 五、智…

设置linux终端用户输入空闲一段时间后就自动断开(linux终端超时自动断开)

在 /etc/profile 中加入TMOUT变量即可。 在文件的最后追加以下两行 export TMOUT600 # 600秒内无操作就断开。 readonly TMOUT # 将变量设置为只读&#xff0c;防止用户更改如图

企业计算机服务器中了rmallox勒索病毒怎么解密,rmallox勒索病毒解密工具流程

在当今数字化时代&#xff0c;越来越多的企业依赖计算机服务器进行办公开展业务&#xff0c;计算机服务器犹如企业的心脏&#xff0c;能够为企业存储许多重要的核心信息&#xff0c;帮助企业有效的开展各项工作业务&#xff0c;提高企业的生产效果&#xff0c;但网络是一把双刃…

springMVC基础使用(示例)

maven依赖&#xff08;javax.servlet-api版本与spring-webmvc班恩要匹配不然会报java.lang.NoSuchMethodError: javax.servlet.http.HttpServletRespons&#xff09;&#xff1a; <dependencies><dependency><groupId>javax.servlet</groupId><arti…

CAN模块开发问题概述

问题一 问题描述 工作环境&#xff1a;ECU外接canoe 操作&#xff1a;使用CANoe模拟发送NM报文&#xff0c;然后停发或者断开CANoe 现象&#xff1a;程序跑死&#xff0c;调用call stack查看压栈情况如下图所示 定位代码如下图所示。可见是由于CAN模块在设置Controller状态时…

计算机毕业设计 | vue+springboot调查问卷管理系统(附源码)

1&#xff0c;研究目的 在进入21世纪以后&#xff0c;互联网得到了蓬勃的发展&#xff0c;电子问卷调查也开始逐渐流行起来。传统纸质问卷和电子问卷相比较后&#xff0c;传统问卷还存在很多弊端&#xff1a; 问卷分发起来比较困难&#xff0c;并且分发试卷耗费大量的金钱和时…

vue3 中vite.config.js相关常用配置详解

base&#xff08;默认为/&#xff09;开发或生产环境服务的公共基础路径。合法的值包括以下几种&#xff1a; 绝对 URL 路径名&#xff0c;例如 /foo/ 部署在根目录下的foo文件下页面路径为&#xff1a; https://foo.com/foo/ 完整的 URL&#xff0c;例如 https://foo.com/&am…

网络安全法中关于网络信息的保护和监管,有哪些规定?

网络安全法作为我们数字时代的重要法律保障&#xff0c;对于网络信息的保护和监管有着明确且详细的规定。这些规定不仅体现了国家对于网络安全的重视&#xff0c;也为我们每个人在数字世界中提供了坚实的法律屏障。 首先&#xff0c;我们来看一个关于网络运营者主体责任的案例。…

np.seterr(divide=‘ignore‘, invalid=‘ignore‘)

这行代码用于设置 NumPy 中的错误处理方式。具体来说&#xff0c;它将在发生除零错误和无效操作错误时忽略这些错误&#xff0c;而不会引发异常。 解释一下参数的含义&#xff1a; divideignore&#xff1a;表示在除零错误发生时忽略该错误。invalidignore&#xff1a;表示在…

vue3组件ref属性用法,动态绑定ref

常见用法 <template><CustomWin ref"sectionRef" class"ref-section"></CustomWin ><button click"xx">点击</button> </template><script setup lang"ts"> import {ref} from vue impor…

uniapp开发安卓app使用文字转语音技术

在 UniApp 开发安卓应用时&#xff0c;要实现文字转语音&#xff08;Text-to-Speech, TTS&#xff09;技术&#xff0c;你可以利用 UniApp 的跨平台能力结合原生模块或第三方服务来实现。以下是一些建议的步骤和方法&#xff1a; 1. 使用 UniApp 原生模块&#xff08;如果支持…

【架构-17】通信系统架构设计理论

通信系统网络架构 1. 局域网网络架构 拓扑结构&#xff1a;星型、总线型、环型、树型。 网络架构&#xff1a;单核心架构&#xff08;结构简单&#xff0c;地理范围受限&#xff09;、双核心架构&#xff08;网络拓扑结构可靠&#xff0c;投资较单核高&#xff09;、环型架构…

更高效的数据交互实现丨 DolphinDB Arrow 插件使用教程

Apache Arrow 是一种跨语言的内存数据交换格式&#xff0c;旨在为用户提供高效的数据结构&#xff0c;以实现在不同的数据处理系统之间共享数据而无需进行复制。它由 Apache 软件基金会开发和维护&#xff0c;目前已经成为许多大型数据处理和分析框架的核心组件之一。在分布式框…

fastapi+vue实现导出功能

1.前端 1.1 tableHTML <el-table :data"userList" style"width: 100%; margin-top: 20px" border selection-change"selectionChange"><el-table-column type"selection" width"50px"/><el-table-column la…

收藏:如何轻松建立CRM系统的帮助中心

大家好&#xff0c;今天咱们来聊聊怎么给公司的CRM系统建个帮助中心。为什么CRM系统需要建立帮助中心呢&#xff1f;很简单&#xff0c;就是为了让客户、员工在遇到问题时能有个快速找到答案的地方&#xff0c;提升咱们的服务质量和内部工作效率。 一、为什么需要建立CRM系统的…

【SQL每日一练】获取PADS公司用户名称和各职业总数并根据格式输出

文章目录 题目一、解析二、题解1.MySQL 题目 生成以下两个结果集&#xff1a; 1、查询 OCCUPATIONS 表中所有名字&#xff0c;紧跟每个职业的第一个字母作为括号&#xff08;即&#xff1a;括在括号中&#xff09;&#xff0c;并按名字顺序排序。例如&#xff1a;AnActorName…

使用 Python 进行图像验证码识别训练及调用

目录 1、验证码识别原理1.1 Tensorflow 介绍1.2 Tensorflow 运行原理1.3 卷积神经网络 CNN&#xff08;Convolutional Neural Networks&#xff09; 2、验证码识别实现步骤2.1 安装第三方模块2.1.1 安装 TensorFlow 模块2.2.2 安装 cuda2.2.3 下载 cudnn 2.2 读取验证码样本形成…

audio 音频标签详解

audio标签详解&#xff08;属性js操作生命周期播放事件&#xff09;_js audio-CSDN博客

Pyside6实操笔记(一):系统页面跳转

文章目录 背景代码实现 背景 假设我们有个登录界面和注册界面&#xff0c;如果我们想要从登录界面跳转到注册界面注册用户名和密码&#xff0c;可以采取本篇博客的方式来实现。 代码实现 关键代码&#xff1a; 1.创建一个空的窗口 class shareInfo:mainwin None跳转代码 …

ShellCode详解三

直接进入正题。 在完成正式的shellcode代码导出之前&#xff0c;我们先手动的对代码进行导出&#xff0c;以使各位同学更加了解其原理。 手动注入shellcode 1、我们利用DLE工具找到上一节中我们生成的PE文件的代码段位置 上述图片就是我们的代码段位置 2、利用WinHex工具我…