随笔- 29 文章- 0 评论- 143 博客园 首页 新随笔 联系 管理 订阅
在上篇《深入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效率更高了吧。
好啦,本篇就到此结束啦!
posted on 2015-04-07 09:12 南轲梦 阅读(7322) 评论(1) 编辑 收藏 发表评论 #1楼 42764742019/6/10 18:14:13 2019-06-10 18:14 | 花溪的小石头 getInputStream() 方法哪里多了一步转换操作了?明明没有转啊
InputStream inputStream = this.fileItem.getInputStream(); 只是用一个变量来接收而已,没有什么问题啊。 支持(0)反对(0) 刷新评论刷新页面 返回顶部 注册用户登录后才能发表评论,请 登录 或 注册, 访问网站首页。 【推荐】超50万C++/C#源码: 大型实时仿真组态图形源码
【前端】SpreadJS表格控件,可嵌入系统开发的在线Excel
【戴尔】Vostro 5481小机身大视野,新一代职场搭档就是它
【推荐】程序员问答平台,解决您开发中遇到的技术难题
|
||||||
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
|---|---|---|---|---|---|---|
| 30 | 1 | 2 | 3 | 4 | 5 | 6 |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 | 1 | 2 | 3 |
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
园龄:4年9个月
粉丝:459
关注:0 +加关注
搜索
常用链接
我的标签
- Mybatis深入浅出系列(13)
- linux(6)
- shell(5)
- Spring Security(4)
- java容器(4)
- springmvc源码(4)
- 权限(2)
- mysql(1)
- maven(1)
随笔分类
随笔档案
- 2017年3月 (1)
- 2017年2月 (5)
- 2017年1月 (2)
- 2016年12月 (2)
- 2016年11月 (1)
- 2015年4月 (2)
- 2015年2月 (1)
- 2014年12月 (1)
- 2014年11月 (2)
- 2014年10月 (12)
积分与排名
- 积分 - 84963
- 排名 - 6364
最新评论
- 1. Re:深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
- 先不管看没看懂,看评论是个好文章[]~( ̄▽ ̄)~*
- --youxiu326
- 2. Re:Mybatis实战之TypeHandler高级进阶
- 看不到图片了
- --SpringBoot1
- 3. Re:shell编程其实真的很简单(五)
- 感谢楼主
- --扁担猫
- 4. Re:深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)
- 感谢楼主!有个问题,如果我想利用mybatis获取动态SQL,但是并不想通过spring+mybatis来执行SQL,因为我想自己控制事务的同时还想动态拿到SQL,该如何使用?
- --侯玺泽
- 5. Re:深入springMVC源码------文件上传源码解析(下篇)
- getInputStream() 方法哪里多了一步转换操作了?明明没有转啊 InputStream inputStream = this.fileItem.getInputStream(); 只是用.......
- --花溪的小石头
阅读排行榜
- 1. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap(91582)
- 2. 深入浅出Mybatis系列(七)---mapper映射文件配置之insert、update、delete(66710)
- 3. 深入浅出Mybatis系列(九)---强大的动态SQL(64933)
- 4. shell编程其实真的很简单(一)(55223)
- 5. 深入浅出Mybatis系列(五)---TypeHandler简介及配置(mybatis源码篇)(49420)
评论排行榜
- 1. 深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)(19)
- 2. 深入浅出Mybatis系列(一)---Mybatis入门(14)
- 3. 深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap(11)
- 4. 深入浅出Mybatis系列(九)---强大的动态SQL(10)
- 5. 话说Spring Security权限管理(源码)(10)