zoukankan      html  css  js  c++  java
  • 深入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方法相比,是不是省了点步骤? 是的,再来一张图,清晰地表示两个方法地不同之处:

    图中:

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

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

  • 相关阅读:
    Python——Jupyter notebook如何更改默认目录
    Python——Jupyter notebook如何输出多行结果
    Matlab提示——未定义与 'double' 类型的输入参数相对应的函数 'resample'
    python读取mat文件:'utf-8' codec can't decode byte 0xb3 in position 38: invalid start byte
    CSDN涨到300一年了
    Windows——管理员方式打开命令提示符
    python——for循环
    java 集合 深度复制多种实现方式和使用注意事项
    Hibernate createQuery查询传递参数的两种方式
    DataTable reload和load区别
  • 原文地址:https://www.cnblogs.com/dongying/p/4390560.html
Copyright © 2011-2022 走看看