SpringBoot下載文件的實(shí)現(xiàn)及速度對比
前言
承上篇上傳文件之后,本文就主要介紹下SpringBoot下下載文件的方式,大致有兩種Outputstream與ResponseEntity,并大概看一下速度對比
文件來源
這里還是以GridFS為例,主要演示的還是從mongo下載下來的文件,如果是本地服務(wù)器上的文件,前端傳以文件路徑直接獲取流即可,如下:
InputStream in = new FileInputStream(System.getProperty("user.dir") + filePath);
接下來就演示下使用GridFsTemplate下載文件,mongo的配置其實(shí)上篇已經(jīng)貼過了,這里就直接貼代碼了,具體的就不做解釋了
@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<ServerAddress> serverAddresses = new ArrayList<>();
serverAddresses.add(new ServerAddress("10.1.61.101:27017"));
// 連接認(rèn)證
MongoCredential credential = MongoCredential.createCredential("root", "admin", "Root_123".toCharArray());
MongoClientOptions.Builder builder = MongoClientOptions.builder();
//最大連接數(shù)
builder.connectionsPerHost(10);
//最小連接數(shù)
builder.minConnectionsPerHost(0);
//超時時間
builder.connectTimeout(1000*3);
// 一個線程成功獲取到一個可用數(shù)據(jù)庫之前的最大等待時間
builder.maxWaitTime(5000);
//此參數(shù)跟connectionsPerHost的乘機(jī)為一個線程變?yōu)榭捎玫淖畲笞枞麛?shù),超過此乘機(jī)數(shù)之后的所有線程將及時獲取一個異常.eg.connectionsPerHost=10 and threadsAllowedToBlockForConnectionMultiplier=5,最多50個線程等級一個鏈接,推薦配置為5
builder.threadsAllowedToBlockForConnectionMultiplier(5);
//最大空閑時間
builder.maxConnectionIdleTime(1000*10);
//設(shè)置池連接的最大生命時間。
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";
}
/**
* 獲取另一個數(shù)據(jù)庫
* @return
*/
public String getFilesDataBaseName() {
return "notifyFiles";
}
/**
* 用于切換不同的數(shù)據(jù)庫
* @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 轉(zhuǎn)為 GridFsResource
* @param gridFsFile
* @return
*/
public GridFsResource convertGridFSFile2Resource(GridFSFile gridFsFile) {
GridFSDownloadStream gridFSDownloadStream = gridFSBucket.openDownloadStream(gridFsFile.getObjectId());
return new GridFsResource(gridFsFile, gridFSDownloadStream);
}
}
對比上篇配置,新增加的兩個方法主要為了應(yīng)對SpringBoot2.x之后,GridFsTemplate的findOne()方法返回從GridFSDBFile改為GridFSFile,導(dǎo)致文件下載時不能使用以前的GridFSDBFile 操作流了,所以加了轉(zhuǎn)換操作
文件下載
分別把兩種方式的下載實(shí)現(xiàn)貼出來
1、OutputStream形式
@RequestMapping(value = "/download2", method = RequestMethod.GET)
public void downLoad2(HttpServletResponse response, String id) {
userService.download2(response, id);
}
controller層如上,只是測試所以很簡略,因?yàn)槭橇鞯男问剿圆⒉恍枰付ㄝ敵龈袷?,下面看下service層實(shí)現(xiàn)
/**
* 以O(shè)utputStream形式下載文件
* @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 變?yōu)?GridFSFile 了,導(dǎo)致下載變得稍微有點(diǎn)繁瑣
GridFSFile gridFSFile = gridFsTemplate.findOne(new Query(Criteria.where("_id").is(id)));
String fileName = gridFSFile.getFilename();
GridFsResource gridFsResource = mongoConfig.convertGridFSFile2Resource(gridFSFile);
// 從此處開始計時
long startTime = System.currentTimeMillis();
InputStream in = null;
OutputStream out = null;
try {
// 這里需對中文進(jìn)行轉(zhuǎn)碼處理
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 .");
}
}
}
可以看到篇幅較長,注釋也已經(jīng)都在代碼里了
2、ResponseEntity形式
@RequestMapping(value = "/download", method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public Object downLoad(String id) {
return userService.download(id);
}
controller需要指定輸出格式application/octet-stream,標(biāo)明是以流的形式下載文件,下面看下service層
/**
* 以ResponseEntity形式下載文件
* @param id
* @return
*/
@Override
public ResponseEntity<byte[]> download(String id) {
GridFsTemplate gridFsTemplate = new GridFsTemplate(mongoConfig.getDbFactory(mongoConfig.getFilesDataBaseName()), mongoTemplate.getConverter());
// 由于springBoot升級到2.x 之后 findOne方法返回由 GridFSDBFile 變?yōu)?GridFSFile 了,導(dǎo)致下載變得稍微有點(diǎn)繁瑣
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();
// 這里需對中文進(jìn)行轉(zhuǎn)碼處理
fileName = new String(fileName.getBytes("utf-8"), "ISO-8859-1");
// 告訴瀏覽器彈出下載對話框
httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);
ResponseEntity<byte[]> 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工具類,依賴如下
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
兩種方式下載速度比較
經(jīng)過測試,當(dāng)文件小于1m內(nèi)兩種方式速度差不多,然后我測了5m的文件,結(jié)果如下:
![]()
![]()
可以看到OutputStream略慢一點(diǎn)點(diǎn),當(dāng)文件再大時這邊也并沒有作測試,總之本人推薦使用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()方法,該方法是返回輸入流中所包含的字節(jié)數(shù),方便在讀寫操作時就能得知數(shù)量,能否使用取決于實(shí)現(xiàn)了InputStream這個抽象類的具體子類中有沒有實(shí)現(xiàn)available這個方法。
如果實(shí)現(xiàn)了那么就可以取得大小,如果沒有實(shí)現(xiàn)那么就獲取不到。
例如FileInputStream就實(shí)現(xiàn)了available方法,那么就可以用new byte[in.available()];這種方式。
但是,網(wǎng)絡(luò)編程的時候Socket中取到的InputStream,就沒有實(shí)現(xiàn)這個方法,那么就不可以使用這種方式創(chuàng)建數(shù)組。
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java?8中讀取文件內(nèi)容?Files.lines()方法使用示例
這篇文章主要介紹了Java?8中讀取文件內(nèi)容Files.lines()方法如何使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
Springboot如何通過路徑映射獲取本機(jī)圖片資源
項目中對圖片的處理與查看是必不可少的,本文將講解如何通過項目路徑來獲取到本機(jī)電腦的圖片資源,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2023-08-08
IDEA中Git版本回退的兩種實(shí)現(xiàn)方案
作為開發(fā)者,代碼版本回退是日常高頻操作,IntelliJ IDEA集成了強(qiáng)大的Git工具鏈,但面對reset和revert兩種核心回退方案,許多開發(fā)者仍存在選擇困惑,本文將解析Reset與Revert兩種方案的操作細(xì)節(jié)及避坑指南,需要的朋友可以參考下2025-03-03
Java實(shí)現(xiàn)對字符串中的數(shù)值進(jìn)行排序操作示例
這篇文章主要介紹了Java實(shí)現(xiàn)對字符串中的數(shù)值進(jìn)行排序操作,涉及java字符串與數(shù)組的相互轉(zhuǎn)換以及數(shù)組排序相關(guān)操作技巧,需要的朋友可以參考下2018-05-05
聽說用了YYYY-MM-dd的程序員,前些天都在加班改Bug
這篇文章主要介紹了YYYY-MM-dd的實(shí)用方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Java中的非對稱加密算法原理與實(shí)現(xiàn)方式
在當(dāng)今的信息時代,數(shù)據(jù)安全已經(jīng)成為了一個至關(guān)重要的問題,加密技術(shù)作為保障信息安全的重要手段,受到了廣泛的應(yīng)用和關(guān)注,本篇文章將詳細(xì)介紹Java中的非對稱加密算法原理及其實(shí)現(xiàn)方式,需要的朋友可以參考下2023-12-12
如何基于java實(shí)現(xiàn)解壓ZIP TAR等文件
這篇文章主要介紹了如何基于java實(shí)現(xiàn)解壓ZIP TAR等文件,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07

