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;
}
}