JAVA 多文件边压缩边下载
最近项目开发中遇到一个问题
下载文件的时候,从数据库中读取的数据太多导致直接抛异常程序错误
这个问题原因主要在我开发之前没有考虑那么多如下这种情况导致:
- 项目在同一时刻导入了大批量的数据至数据库中,比如说1G
然后在项目导出的时候,这一时刻的数据都被一次性查询加载到内存中,直接撑爆JVM虚拟机内存,然后就出现OOM异常了
后面我就想着分批到数据库中查找,然后将查找到的数据追加到同一个文件中
实现了一半后我发现,前面想的是没啥问题,但是后面等到把服务器中加载数据库数据完的临时文件以文件流的方式加载内存中并返回给前台的时候又会发现OOM异常,JVM虚拟机内存中加载不了这么大的文件
最后我想着能不能将在生成临时文件的时候多生成几份,于是我按照我想的要求将用于保存数据库中查询数据的临时文件分成了不等份,再将这些文件返回给前台 实现的时候发现,前台只能接收一次文件下载
最终找到了比较好的一种方法,就是多文件边压缩边下载的方法:
生成多文件的思路上面已经提过了,这里我就不做过多概述,主要是怎样达到多文件边压缩边下载的效果
/**
* 多文件边解压边下载,并在下载完之后把服务器文件删除掉
* @param list 文件路径列表
*/
public static void batchDownloadFiles(List<String> list) throws Exception{
HttpServletResponse response = WebUtils.getResponse();
//设置浏览器返回文件信息
String downloadName = DateUtils.getStrDate("yyyyMMddHHmmss")+".zip";
response.setHeader("Content-Disposition", "attachment;fileName=\"" + downloadName + "\"");
//设置压缩流:直接写入response,实现边压缩边下载
final ZipOutputStream zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
zipos.setMethod(ZipOutputStream.DEFLATED); //设置压缩方法
//循环将文件写入压缩流
final DataOutputStream os = new DataOutputStream(zipos);
//这里使用java8的流操作,效率更快,代码更美观
list.stream().map(e->{
File file = new File(e);
try {
//添加ZipEntry,并ZipEntry中写入文件流
//还要防止下载的文件有重名的导致下载失败
//这里的e表示文件路径+文件名,通过截取获取到文件名
zipos.putNextEntry(new ZipEntry(e.substring(e.lastIndexOf("/")+1)));
InputStream is = new FileInputStream(file);
byte[] b = new byte[100];
int length = 0;
while((length = is.read(b))!= -1){
os.write(b, 0, length);
}
is.close();
zipos.closeEntry();
} catch (IOException e2) {
e2.printStackTrace();
}finally {
//下载完之后把服务器文件删除掉
FileUtils.delFile(file);
}
return null;
}).count();
//关闭流
os.flush();
os.close();
zipos.close();
}
将文件路径列表保存到List数组中之后,再调用此方法
本人收集整理具体工具类:
DownloadFileUtils.java
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* 文件下载工具类
*
*/
public class DownloadFileUtils {
/**
* 把byte数组数据转换成文件
* @param response
* @param bytes 上传文件转换的字节数组
* @param fileName 上传文件的文件名 格式 文件名.文件类型 ,如 abc.txt
* @throws IOException
*/
public static void getFileByBytes(HttpServletResponse response, byte[] bytes, String fileName) throws IOException {
//此处需要设置ISO8859-1,application/octet-stream为未知文件类型时使用
response.setContentType("application/octet-stream;charset=ISO8859-1");
BufferedOutputStream output = null;
//将文件以文件流的方式输出
output = new BufferedOutputStream(response.getOutputStream());
String fileNameDown = new String(fileName.getBytes(), "ISO8859-1");
//fileNameDown上面得到的文件名
response.setHeader("Content-Disposition", "attachment;filename=" +
fileNameDown);
output.write(bytes);
response.flushBuffer();
output.flush();
output.close();
}
/**
* 把byte数组数据转换成文件
* @param bytes 上传文件转换的字节数组
* @param fileName 上传文件的文件名 格式 文件名.文件类型 ,如 abc.txt
* @throws IOException
*/
public static void getFileByBytes(byte[] bytes, String fileName) throws IOException {
HttpServletResponse response = WebUtils.getResponse();
//此处需要设置ISO8859-1,application/octet-stream为未知文件类型时使用
response.setContentType("application/octet-stream;charset=ISO8859-1");
BufferedOutputStream output = null;
//将文件以文件流的方式输出
output = new BufferedOutputStream(response.getOutputStream());
String fileNameDown = new String(fileName.getBytes(), "ISO8859-1");
//fileNameDown上面得到的文件名
response.setHeader("Content-Disposition", "attachment;filename=" +
fileNameDown);
output.write(bytes);
output.flush();
output.close();
}
/**
* 把文件流转换成文件
* @param in 文件流
* @param fileName 上传文件的文件名 格式 文件名.文件类型 ,如 abc.txt
* @throws IOException
*/
public static void getFileByInputStream(InputStream in, String fileName) throws IOException {
getFileByBytes(IoUtil.toByteArray(in), fileName);
}
/**
* 文件下载
* @param bytes 上传文件转换的字节数组
* @param fileName 上传文件的文件名 格式 文件名.文件类型 ,如 abc.txt
* @return
*/
public static ResponseEntity<byte[]> getResponseEntityByByte(byte[] bytes, String fileName){
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentDispositionFormData("attachment", fileName);
//设置MIME类型
httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
return new ResponseEntity<byte[]>(bytes, httpHeaders, HttpStatus.OK);
}
/**
* 文件下载
* @param in 文件流
* @param fileName 上传文件的文件名 格式 文件名.文件类型 ,如 abc.txt
* @return
* @throws IOException
*/
public static ResponseEntity<byte[]> getResponseEntityByByte(InputStream in, String fileName) throws IOException {
return getResponseEntityByByte(IoUtil.toByteArray(in), fileName);
}
/**
* 分批将数据写入到指定文件中
* @param filePath 文件路径
* @param fileName 文件名称
* @param bytes 数据
* @throws IOException
*/
public static void putBytesToFile(String filePath, String fileName, byte[] bytes) throws IOException {
File file = new File(filePath+fileName);
if (!file.exists()) {
file.createNewFile();
}
BufferedOutputStream output = null;
output = new BufferedOutputStream(new FileOutputStream(file, true));
output.write(bytes);
output.flush();
output.close();
}
/**
* 多文件边解压边下载,并在下载完之后把服务器文件删除掉
* @param list 文件路径列表
*/
public static void batchDownloadFiles(List<String> list) throws Exception{
HttpServletResponse response = WebUtils.getResponse();
//设置浏览器返回文件信息
String downloadName = DateUtils.getStrDate("yyyyMMddHHmmss")+".zip";
response.setHeader("Content-Disposition", "attachment;fileName=\"" + downloadName + "\"");
//设置压缩流:直接写入response,实现边压缩边下载
final ZipOutputStream zipos = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()));
zipos.setMethod(ZipOutputStream.DEFLATED); //设置压缩方法
//循环将文件写入压缩流
final DataOutputStream os = new DataOutputStream(zipos);
//这里使用java8的流操作,效率更快,代码更美观
list.stream().map(e->{
File file = new File(e);
try {
//添加ZipEntry,并ZipEntry中写入文件流
//还要防止下载的文件有重名的导致下载失败
//这里的e表示文件路径+文件名,通过截取获取到文件名
zipos.putNextEntry(new ZipEntry(e.substring(e.lastIndexOf("/")+1)));
InputStream is = new FileInputStream(file);
byte[] b = new byte[100];
int length = 0;
while((length = is.read(b))!= -1){
os.write(b, 0, length);
}
is.close();
zipos.closeEntry();
} catch (IOException e2) {
e2.printStackTrace();
}finally {
//下载完之后把服务器文件删除掉
FileUtils.delFile(file);
}
return null;
}).count();
//关闭流
os.flush();
os.close();
zipos.close();
}
}
WebUtils
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 扩展 <code>org.springframework.web.util.WebUtils</code>
*
* @since 1.0
*/
public abstract class WebUtils extends org.springframework.web.util.WebUtils {
public static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
}
/**
* spring版本较低,不支持该方法
* @return
*/
public static HttpServletResponse getResponse() {
return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
public static HttpSession getSession() {
return getRequest().getSession();
}
public static ServletContext getServletContext() {
return ContextLoader.getCurrentWebApplicationContext().getServletContext();
}
public static String getContextPath() {
return getRequest().getContextPath();
}
public static String getCookieValue(HttpServletRequest request, String key) {
Cookie cookie = getCookie(request, key);
if(cookie != null) {
return cookie.getValue();
}
return "";
}
public static String getSessionIdWithCookie(HttpServletRequest request, String cookieKey) {
String sessionId = request.getSession().getId();
Cookie cookie = getCookie(request, cookieKey);
if(cookie != null) {
sessionId = cookie.getValue();
}
return sessionId;
}
/**
* 获取当前请求的ip地址
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request){
String ip = request.getHeader("X-Real-IP");
if(!StringUtils.isBlank(ip)&&!"unknown".equalsIgnoreCase(ip))
{
return ip;
}
ip = request.getHeader("X-Forwarded-For");
if(!StringUtils.isBlank(ip)&& !"unknown".equalsIgnoreCase(ip))
{
int index = ip.indexOf(',');
if(index != -1){
return ip.substring(0, index);
}else{
return ip;
}
}
ip = request.getHeader("Proxy-Client-IP");
if(!StringUtils.isBlank(ip)&&!"unknown".equalsIgnoreCase(ip))
{
return ip;
}
ip = request.getHeader("WL-Proxy-Client-IP");
if(!StringUtils.isBlank(ip)&&!"unknown".equalsIgnoreCase(ip))
{
return ip;
}
ip = request.getRemoteAddr();
if("0:0:0:0:0:0:0:1".equals(ip)){
ip = "127.0.0.1";
}
return ip;
}
}