SpringBoot下载文件的实现及速度对比

网友投稿 925 2022-11-18

SpringBoot-文件的实现及速度对比

SpringBoot-文件的实现及速度对比

目录前言文件来源文件-1、OutputStream形式2、ResponseEntity形式两种方式-速度比较后话

前言

承上篇上传文件之后,本文就主要介绍下SpringBoot下-文件的方式,大致有两种Outputstream与ResponseEntity,并大概看一下速度对比

文件来源

这里还是以GridFS为例,主要演示的还是从mongo-下来的文件,如果是本地服务器上的文件,前端传以文件路径直接获取流即可,如下:

InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);

接下来就演示下使用GridFsTemplate-文件,mongo的配置其实上篇已经贴过了,这里就直接贴代码了,具体的就不做解释了

@Service

@Slf4j

public class MongoConfig extends AbstractMongoConfiguration {

@Autowired

private MongoTemplate mongoTemplate;

@Autowired

private GridFSBucket gridFSBucket;

@Override

public MongoClient mongoClient() {

MongoClient mongoClient = getMongoClient();

return mongoClient;

}

public MongoClient getMongoClient() {

// MongoDB地址列表

List serverAddresses = new ArrayList<>();

serverAddresses.add(new ServerAddress("10.1.61.101:27017"));

// 连接认证

MongoCredential credential = MongoCredential.createCredential("root", "admin", "Root_123".toCharArray());

MongoClientOptions.Builder builder = MongoClientOptions.builder();

//最大连接数

builder.connectionsPerHost(10);

//最小连接数

builder.minConnectionsPerHost(0);

//超时时间

builder.connectTimeout(1000*3);

// 一个线程成功获取到一个可用数据库之前的最大等待时间

builder.maxWaitTime(5000);

//此参数跟connectionsPerHost的乘机为一个线程变为可用的最大阻塞数,超过此乘机数之后的所有线程将及时获取一个异常.eg.connectionsPerHost=10 and threadsAllowedToBlockForConnectionMultiplier=5,最多50个线程等级一个链接,推荐配置为5

builder.threadsAllowedToBlockForConnectionMultiplier(5);

//最大空闲时间

builder.maxConnectionIdleTime(1000*10);

//设置池连接的最大生命时间。

builder.maxConnectionLifeTime(1000*10);

//连接超时时间

builder.socketTimeout(1000*10);

MongoClientOptions myOptions = builder.build();

MongoClient mongoClient = new MongoClient(serverAddresses, credential, myOptions);

return mongoClient;

}

@Override

protected String getDatabaseName() {

return "notifyTest";

}

/**

* 获取另一个数据库

* @return

*/

public String getFilesDataBaseName() {

return "notifyFiles";

}

/**

* 用于切换不同的数据库

* @return

*/

public MongoDbFactory getDbFactory(String dataBaseName) {

MongoDbFactory dbFactory = null;

try {

dbFactory = new SimpleMongoDbFactory(getMongoClient(), dataBaseName);

} catch (Exception e) {

log.error("Get mongo client have an error, please check reason...", e.getMessage());

}

return dbFactory;

}

/**

* 获取文件存储模块

* @return

*/

public GridFsTemplate getGridFS() {

return new GridFsTemplate(getDbFactory(getFilesDataBaseName()), mongoTemplate.getConverter());

}

@Bean

public GridFSBucket getGridFSBuckets() {

MongoDatabase db = getDbFactory(getFilesDataBaseName()).getDb();

return GridFSBuckets.create(db);

}

/**

* 为了解决springBoot2.0之后findOne方法返回类更改所新增 将GridFSFile 转为 GridFsResource

* @param gridFsFile

* @return

*/

public GridFsResource convertGridFSFile2Resource(GridFSFile gridFsFile) {

GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFsFile.getObjectId());

return new GridFsResource(gridFsFile, gridFSDownloadStream);

}

}

对比上篇配置,新增加的两个方法主要为了应对SpringBoot2.x之后,GridFsTemplate的findOne()方法返回从GridFSDBFile改为GridFSFile,导致文件-时不能使用以前的GridFSDBFile 操作流了,所以加了转换操作

文件-

分别把两种方式的-实现贴出来

1、OutputStream形式

@RequestMapping(value = "/download2", method = RequestMethod.GET)

public void downLoad2(HttpServletResponse response, String id) {

userService.download2(response, id);

}

controller层如上,只是测试所以很简略,因为是流的形式所以并不需要指定输出格式,下面看下service层实现

/**

* 以OutputStream形式-文件

* @param response

* @param id

*/

@Override

public void download2(HttpServletResponse response, String id) {

GridFsTemplate gridFsTemplate = new GridFsTemplate(mongoConfig.getDbFactory(mongoConfig.getFilesDataBaseName()), mongoTemplate.getConverter());

// 由于springBoot升级到2.x 之后 findOne方法返回由 GridFSDBFile 变为 GridFSFile 了,导致-变得稍微有点繁琐

GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));

String fileName = gridFSFile.getFilename();

GridFsResource gridFsResource = mongoConfig.convertGridFSFile2Resource(gridFSFile);

// 从此处开始计时

long startTime = System.currentTimjlcOYveMillis();

InputStream in = null;

OutputStream out = null;

try {

// 这里需对中文进行转码处理

fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");

// 告诉浏览器弹出-对话框

response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

byte[] buffer = new byte[1024];

int len;

// 获得输出流

out = response.getOutputStream();

in = gridFsResource.getInputStream();

while ((len = in.read(buffer)) > 0) {

out.write(buffer, 0 ,len);

}

} catch (IOException e) {

log.error("transfer in error .");

} finally {

try {

if (null != in)

in.close();

if (null != out)

out.close();

log.info("download file with stream total time : {}", System.currentTimeMillis() - startTime);

} catch (IOException e){

log.error("close IO error .");

}

}

}

可以看到篇幅较长,注释也已经都在代码里了

2、ResponseEntity形式

@RequestMapping(value = "/download", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)

public Object downLoad(Strinhttp://g id) {

return userService.download(id);

}

controller需要指定输出格式application/octet-stream,标明是以流的形式-文件,下面看下service层

/**

* 以ResponseEntity形式-文件

* jlcOYv@param id

* @return

*/

@Override

public ResponseEntity download(String id) {

GridFsTemplate gridFsTemplate = new GridFsTemplate(mongoConfig.getDbFactory(mongoConfig.getFilesDataBaseName()), mongoTemplate.getConverter());

// 由于springBoot升级到2.x 之后 findOne方法返回由 GridFSDBFile 变为 GridFSFile 了,导致-变得稍微有点繁琐

GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));

String fileName = gridFSFile.getFilename();

GridFsResource gridFsResource = mongoConfig.convertGridFSFile2Resource(gridFSFile);

// 从此处开始计时

long startTime = System.currentTimeMillis();

try {

InputStream in = gridFsResource.getInputStream();

// 请求体

byte[] body = IOUtils.toByteArray(in);

// 请求头

HttpHeaders httpHeaders = new HttpHeaders();

// 这里需对中文进行转码处理

fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");

// 告诉浏览器弹出-对话框

httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);

ResponseEntity responseEntity = new ResponseEntity<>(body, httpHeaders, HttpStatus.OK);

log.info("download file total with ResponseEntity time : {}", System.currentTimeMillis() - startTime);

return responseEntity;

} catch (IOException e) {

log.error("transfer in error .");

}

return null;

}

上面用到了IOUtils工具类,依赖如下

commons-io

commons-io

2.4

两种方式-速度比较

经过测试,当文件小于1m内两种方式速度差不多,然后我测了5m的文件,结果如下:

可以看到OutputStream略慢一点点,当文件再大时这边也并没有作测试,总之本人推荐使用ResponseEntity形式-文件~

后话

如果只是想显示某个路径下的图片而并不需要-,那么采用如下形式:

@RequestMapping(value = "/application/file/show", method = RequestMethod.GET, produces = MediaType.IMAGE_PNG_VALUE)

public Object downloadFile(@RequestParam("path") String filePath) {

try {

InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);

byte[] bytes = new byte[in.available()];

in.read(bytes);

return bytes;

} catch (IOException e) {

log.error("transfer byte error");

return buildMessage(ResultModel.FAIL, "show pic error");

}

}

需要注意上述的available()方法,该方法是返回输入流中所包含的字节数,方便在读写操作时就能得知数量,能否使用取决于实现了InputStream这个抽象类的具体子类中有没有实现available这个方法。

如果实现了那么就可以取得大小,如果没有实现那么就获取不到。

例如FileInputStream就实现了available方法,那么就可以用new byte[in.available()];这种方式。

但是,网络编程的时候Socket中取到的InputStream,就没有实现这个方法,那么就不可以使用这种方式创建数组。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:容器和Docker
下一篇:Docker基础
相关文章

 发表评论

暂时没有评论,来抢沙发吧~