Java开发:文件的上传和下载
文章目录
一、文件上传
-
使用
MultipartFile
类型接收参数; -
调用上传有两种方式:
方式一:使用
curl
命令curl -F "file=@/data/filename.txt" http://localhost:8080/upload --verbose
方式二:使用html,写一个form表单:
同样是POST请求,为何文件上传的功能会慢?其中一个原因是, 使用
multipart/form-data
编码,需要对数据进行分块处理,增加时间开销。服务器端也需要解析这些分块数据,并将其转化为文件或其它形式处理,这个过程也会增加时间开销。<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Upload</title> </head> <body> <div style="margin-left: 300px; margin-top: 50px;"> <form action="/upload" method="post" enctype="multipart/form-data" accept-charset="UTF-8"> <input type="file" name="file"/><br/><br/> <button type="submit">上传</button> </form> </div> </body> </html>
-
Java后端上传示例:
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public String upload(@RequestParam("file") MultipartFile file) {
try {
String dirPath = uploadPath + File.separator + port;
File dirFile = new File(dirPath);
if (!dirFile.exists()) {
boolean ok = dirFile.mkdirs();
log.debug("===> {} create dir {}", ok, dirPath);
}
String fileName = file.getOriginalFilename();
String filePath = dirPath + File.separator + fileName;
File dest = new File(filePath);
file.transferTo(dest);
log.debug("===> success upload file: {}", filePath);
return dest.getName();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
二、文件下载
(1)使用BufferedInputStream的好处
-
提高读取效率:在内存中维护一个缓冲区(buffer),默认8kb,一次性读取一个较大的块。后续从缓冲区中读取数据;
read() 方法,读取一个字节
-
支持标记和重置:mark和reset方法
(2)@RequestMapping(value = "/download")
不写method参数,让其支持所有的HTTP方法,无论是GET、POST、PUT还是其它。
(3)下载文件响应头设置,以及解决中文文件名正常下载
- 指定字符集;
- 指定文件类型;
- 指定文件名。注意,对文件名进行编码,解决中文文件名正常下载;
// 需要添加一些头信息,响应才知道是下载的文件
// 添加字符集
response.setCharacterEncoding("UTF-8");
// 文件类型: 方式一:指定具体的文件类型;方式二:指定其是一个二进制格式
// 其它的文件类型:text/plain、application/pdf、application/vnd.ms-excel、image/jpeg、image/png
response.setContentType("application/octet-stream");
// 文件的名称,解决中文乱码
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
// attachment (附件) 提示浏览器下载, inline 提示浏览器显示内容(如果支持的话)
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
// 文件的长度
response.setContentLength((int) file.length());
(4)后端两种写下载的方式:返回void和返回byte[]
返回byte[]
:需要在内存中存储整个文件内容,对于大文件可能会导致内存问题。
代码示例:
@RequestMapping(value = "/downloadBytes")
@ResponseBody
public byte[] downloadBytes(@RequestParam("fileName") String fileName, HttpServletResponse response) {
String dirPath = uploadPath + File.separator + port;
String filePath = dirPath + File.separator + fileName;
File file = new File(filePath);
if (!file.exists() && file.isFile()) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return null;
}
checkState(file.exists() && file.isFile(), "file not exists: %s", filePath);
// 需要添加一些头信息,响应才知道是下载的文件
// 添加字符集
response.setCharacterEncoding("UTF-8");
// 文件类型: 方式一:指定具体的文件类型;方式二:指定其是一个二进制格式
// 其它的文件类型:text/plain、application/pdf、application/vnd.ms-excel、image/jpeg、image/png
response.setContentType("application/octet-stream");
// 文件的名称,解决中文乱码
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
// attachment (附件) 提示浏览器下载, inline 提示浏览器显示内容(如果支持的话)
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
try (InputStream inputStream = new FileInputStream(file)) {
byte[] bytes = new byte[(int) file.length()];
int readLen = inputStream.read(bytes);
log.debug("====> readLen {}", readLen);
return bytes;
} catch (Exception e) {
log.error(e.getMessage());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return null;
}
}
返回 void
的方式通常会使用流式传输(streaming)的方式来发送文件内容,这意味着文件是以一段一段的方式发送的。这样做有几个好处:
- 节省内存:整个文件不需要一次性加载到内存中,减少了内存占用。对于大文件尤其重要。
- 更高效:可以立即开始传输文件的部分内容,而不需要等待整个文件加载完成。
代码示例:
/**
* 下载
*
* @param fileName
* @param response
*/
@RequestMapping(value = "/download")
public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {
String dirPath = uploadPath + File.separator + port;
String filePath = dirPath + File.separator + fileName;
File file = new File(filePath);
if (!(file.exists() && file.isFile())) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
log.debug("====> file not exists: {}", filePath);
return;
}
// 需要添加一些头信息,响应才知道是下载的文件
// 添加字符集
response.setCharacterEncoding("UTF-8");
// 文件类型: 方式一:指定具体的文件类型;方式二:指定其是一个二进制格式
// 其它的文件类型:text/plain、application/pdf、application/vnd.ms-excel、image/jpeg、image/png
response.setContentType("application/octet-stream");
// 文件的名称,解决中文乱码
String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
// attachment (附件) 提示浏览器下载, inline 提示浏览器显示内容(如果支持的话)
response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
// 文件的长度
response.setContentLength((int) file.length());
try (InputStream inputStream = new FileInputStream(file);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) {
byte[] bytes = new byte[1024 * 16];
int len = 0;
ServletOutputStream outputStream = response.getOutputStream();
while ((len = bufferedInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
outputStream.flush();
} catch (Exception e) {
log.error(e.getMessage());
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}