在上篇《深入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效率更高了吧。
好啦,本篇就到此結束啦!
新聞熱點
疑難解答