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

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

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

@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方法:mvc

CommonsMultipartFile:app

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)的对应方法:ide

DiskFileItem:this

public InputStream getInputStream()
        throws IOException {
        if (!isInMemory()) {
            return new FileInputStream(dfos.getFile());
        }

        if (cachedContent == null) {
            cachedContent = dfos.getData();
        }
        return new ByteArrayInputStream(cachedContent);
    }

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

 

接下来,我们再看看 CommonsMultipartFile 的 transferTo 方法,以便造成比较:debug

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效率更高了吧。

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

相关文章
相关标签/搜索