Spring MVC 提供兩種多文件上傳路徑:一是手動以字節流讀寫,二是利用 CommonsMultipartResolver 解析器自動綁定。本文通過實測對比兩者在相同文件下的耗時差異,揭示效率懸殊的根本原因。
commons-fileupload-1.2.0.jar、commons-io-1.4.0.jarmultipartResolver Bean,設定 maxUploadSize、maxInMemorySizeweb.xml 配置 DispatcherServlet 與 CharacterEncodingFilter頁面包含兩個獨立表單,分別提交至 /file/upload(字節流)與 /file/upload2(解析器),均支援動態新增/刪除文件輸入框。
upload()@RequestMapping("/upload")
public String addUser(@RequestParam("file") CommonsMultipartFile[] files, HttpServletRequest request) {
for (CommonsMultipartFile file : files) {
if (!file.isEmpty()) {
long pre = System.currentTimeMillis();
FileOutputStream os = new FileOutputStream("H:/" + new Date().getTime() + file.getOriginalFilename());
FileInputStream in = (FileInputStream) file.getInputStream();
int b;
while ((b = in.read()) != -1) { os.write(b); } // 逐字節寫入
os.close(); in.close();
System.out.println("耗時:" + (System.currentTimeMillis() - pre) + "ms");
}
}
return "/success";
}
upload2()@RequestMapping("/upload2")
public String upload2(HttpServletRequest request) throws IOException {
CommonsMultipartResolver resolver = new CommonsMultipartResolver(request.getSession().getServletContext());
if (resolver.isMultipart(request)) {
MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request;
Iterator<String> iter = multiRequest.getFileNames();
while (iter.hasNext()) {
long pre = System.currentTimeMillis();
MultipartFile file = multiRequest.getFile(iter.next());
if (file != null && StringUtils.isNotBlank(file.getOriginalFilename())) {
file.transferTo(new File("H:/demoUpload" + file.getOriginalFilename())); // 一鍵轉存
}
System.out.println("耗時:" + (System.currentTimeMillis() - pre) + "ms");
}
}
return "/success";
}
| 方式 | 文件 1 | 文件 2 | 文件 3 |
|---|---|---|---|
| 字節流逐字節寫入 | 534 ms | 453 ms | 387 ms |
解析器 transferTo | 2 ms | 1 ms | 2 ms |
in.read() / os.write(b) 逐字節複製,JNI 開銷極大,CPU 佔用高,磁盤 I/O 碎片化。MultipartFile.transferTo() 內部使用 FileChannel.transferTo 或 NIO 直接內存映射,實現零拷貝,系統調用極少,磁盤連續寫入。maxInMemorySize 閾值自動切換內存/磁盤存儲。