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

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

相關文章
相關標籤/搜索