深入springMVC源码------文件上传源码解析(下篇)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 深入springMVC源码------文件上传源码解析(下篇)

在上篇《深入springMVC------文件上传源码解析(上篇) 》中,介绍了springmvc文件上传相关。那么本篇呢,将进一步介绍springmvc 上传文件的效率问题。

相信大部分人在处理文件上传逻辑的时候会直接获取输入流直接进行操作,伪代码类似这样:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResultView upload(@RequestParam("file") MultipartFile file) {
    Inputstream in = file.getInputStream();
    ...         

但是,出于效率,其实我个人更推荐使用 MultipartFile 的 transferTo 方法进行操作,类似这样:

@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResultView upload(@RequestParam("file") MultipartFile file) {
    file.transferTo(new File(destFile));
    ...         

为什么呢?这个就得从源码说起,废话不多说,咱们直接去看源码吧:

1. 先看 MultipartFile(其实现类CommonsMultipartFile) 的getInputStream方法:

CommonsMultipartFile:

public InputStream getInputStream() throws IOException {
        if (!isAvailable()) {
            throw new IllegalStateException("File has been moved - cannot be read again");
        }
        InputStream inputStream = this.fileItem.getInputStream();
        return (inputStream != null ? inputStream : new ByteArrayInputStream(new byte[0]));
    }

通过源码可以看到,spring是通过commons-fileupload 中的FileItem对象去获取输入流,那么就去看看FileItem(其实现类DiskFileItem)的对应方法:

DiskFileItem:

public InputStream getInputStream()
        throws IOException {
        if (!isInMemory()) {
            return new FileInputStream(dfos.getFile());
        }
        if (cachedContent == null) {
            cachedContent = dfos.getData();
        }
        return new ByteArrayInputStream(cachedContent);
    }

通过源码可以看到:先去查看是否存在于内存中,如果存在,就将内存中的file对象包装为文件流, 如果不存在,那么就去看缓存,如果缓存存在就从缓存中获取字节数组并包装为输入流。

 

接下来,咱们再看看 CommonsMultipartFile 的 transferTo 方法,以便形成比较:

CommonsMultipartFile:

@Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        if (!isAvailable()) {
            throw new IllegalStateException("File has already been moved - cannot be transferred again");
        }
        if (dest.exists() && !dest.delete()) {
            throw new IOException(
                    "Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted");
        }
        try {
            this.fileItem.write(dest);
            if (logger.isDebugEnabled()) {
                String action = "transferred";
                if (!this.fileItem.isInMemory()) {
                    action = isAvailable() ? "copied" : "moved";
                }
                logger.debug("Multipart file '" + getName() + "' with original filename [" +
                        getOriginalFilename() + "], stored " + getStorageDescription() + ": " +
                        action + " to [" + dest.getAbsolutePath() + "]");
            }
        }
        catch (FileUploadException ex) {
            throw new IllegalStateException(ex.getMessage());
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            logger.error("Could not transfer to file", ex);
            throw new IOException("Could not transfer to file: " + ex.getMessage());
        }
    }

不多说,主要看 this.fileItem.write(dest) 这一句,利用commons-fileupload 中的相关方法:

DiskFileItem:

public void write(File file) throws Exception {
        if (isInMemory()) {
            FileOutputStream fout = null;
            try {
                fout = new FileOutputStream(file);
                fout.write(get());
            } finally {
                if (fout != null) {
                    fout.close();
                }
            }
        } else {
            File outputFile = getStoreLocation();
            if (outputFile != null) {
                // Save the length of the file
                size = outputFile.length();
........

通过源码可以看到 transfoTo 方法很干净利落,直接去将内存中的文件通过输出流写出到指定的file 。 等等,跟上面的 getInputStream方法相比,是不是省了点步骤? 是的,再来一张图,清晰地表示两个方法地不同之处:

微信截图_20230530131341.png

图中:

红色线表示使用的是transferTo方法,黑色线代表getInputStream方法, 可见,transferTo直接将内存中的文件缓存直接写入到磁盘的物理文件, 而getInputStream方法会中转一次(先通过getInputStream从内存中获取流,再通过outputStream输出到磁盘物理文件)。两者相比,即使从步骤来看,你也能看出来transferTo效率更高了吧。

好啦,本篇就到此结束啦!

相关文章
|
26天前
|
负载均衡 算法 Java
Spring Cloud全解析:负载均衡算法
本文介绍了负载均衡的两种方式:集中式负载均衡和进程内负载均衡,以及常见的负载均衡算法,包括轮询、随机、源地址哈希、加权轮询、加权随机和最小连接数等方法,帮助读者更好地理解和应用负载均衡技术。
|
10天前
|
Java 对象存储 开发者
解析Spring Cloud与Netflix OSS:微服务架构中的左右手如何协同作战
Spring Cloud与Netflix OSS不仅是现代微服务架构中不可或缺的一部分,它们还通过不断的技术创新和社区贡献推动了整个行业的发展。无论是对于初创企业还是大型组织来说,掌握并合理运用这两套工具,都能极大地提升软件系统的灵活性、可扩展性以及整体性能。随着云计算和容器化技术的进一步普及,Spring Cloud与Netflix OSS将继续引领微服务技术的发展潮流。
25 0
|
21天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
25天前
|
开发工具
Flutter-AnimatedWidget组件源码解析
Flutter-AnimatedWidget组件源码解析
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
176 37
|
13天前
|
编解码 开发工具 UED
QT Widgets模块源码解析与实践
【9月更文挑战第20天】Qt Widgets 模块是 Qt 开发中至关重要的部分,提供了丰富的 GUI 组件,如按钮、文本框等,并支持布局管理、事件处理和窗口管理。这些组件基于信号与槽机制,实现灵活交互。通过对源码的解析及实践应用,可深入了解其类结构、布局管理和事件处理机制,掌握创建复杂 UI 界面的方法,提升开发效率和用户体验。
64 12
|
24天前
|
XML 监控 Java
Spring Cloud全解析:熔断之Hystrix简介
Hystrix 是由 Netflix 开源的延迟和容错库,用于提高分布式系统的弹性。它通过断路器模式、资源隔离、服务降级及限流等机制防止服务雪崩。Hystrix 基于命令模式,通过 `HystrixCommand` 封装对外部依赖的调用逻辑。断路器能在依赖服务故障时快速返回备选响应,避免长时间等待。此外,Hystrix 还提供了监控功能,能够实时监控运行指标和配置变化。依赖管理方面,可通过 `@EnableHystrix` 启用 Hystrix 支持,并配置全局或局部的降级策略。结合 Feign 可实现客户端的服务降级。
101 23
|
8天前
|
存储 缓存 Java
在Spring Boot中使用缓存的技术解析
通过利用Spring Boot中的缓存支持,开发者可以轻松地实现高效和可扩展的缓存策略,进而提升应用的性能和用户体验。Spring Boot的声明式缓存抽象和对多种缓存技术的支持,使得集成和使用缓存变得前所未有的简单。无论是在开发新应用还是优化现有应用,合理地使用缓存都是提高性能的有效手段。
14 1
|
2月前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
2月前
|
存储 NoSQL Redis
redis 6源码解析之 object
redis 6源码解析之 object
58 6

热门文章

最新文章

推荐镜像

更多
下一篇
无影云桌面