java批量檔案打包成壓縮成zip下載和大量資料匯出excel時的處理方法
阿新 • • 發佈:2019-02-20
對於我們來說,java匯出資料成excel或其他資料檔案,或者下載資源是開發中的家常便飯, 但是在匯出的時候,如果點選一個按鈕匯出幾百萬條資料,如果不作處理的話很可能會出現一系列的問題. 這裡介紹打包成zip壓縮包下載
針對大量資料匯出excel, 這裡有幾種辦法:
1. 每到一定數量就分成一個sheet
2. 每到一定數量分成一個excel,壓縮成zip包打包下載
3. 控制匯出的資料量,或者分頁查詢
4. 加快取,用POI官方的api
SXSSFWorkbook workbook = new SXSSFWorkbook(rowCache);
我這邊用的是2和4 , 整體的思路就是,如果是小數目的資料,直接下載excel,如果是大量資料,先轉換成一個個excle放在一個臨時資料夾中, 然後打包臨時資料夾成zip包返回給瀏覽器,然後刪除臨時資料夾
[下載檔案的工具類]
先準備好自己定義的工具類,之後的方法裡面都需要用到這個工具類, 包括設定兩頭一流, 壓縮檔案,刪除資料夾等
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.BASE64Encoder;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@SuppressWarnings("restriction")
public class FileDownloadUtils {
private static final Logger logger = LoggerFactory.getLogger(FileDownloadUtils.class);
/**
* 編譯下載的檔名
* @param filename
* @param agent
* @return
* @throws IOException
*/
public static String encodeDownloadFilename(String filename, String agent)throws IOException {
if (agent.contains("Firefox")) { // 火狐瀏覽器
filename = "=?UTF-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
filename = filename.replaceAll("\r\n", "");
} else { // IE及其他瀏覽器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+"," ");
}
return filename;
}
/**
* 建立資料夾;
* @param path
*/
public static void createFile(String path) {
File file = new File(path);
//判斷檔案是否存在;
if (!file.exists()) {
//建立檔案;
file.mkdirs();
}
}
/**
* 生成.zip檔案;
* @param path
* @throws IOException
*/
public static ZipOutputStream craeteZipPath(String path) throws IOException{
ZipOutputStream zipOutputStream = null;
File file = new File(path+DateUtils.getDateWx()+".zip");
zipOutputStream = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
File[] files = new File(path).listFiles();
FileInputStream fileInputStream = null;
byte[] buf = new byte[1024];
int len = 0;
if(files!=null && files.length > 0){
for(File excelFile:files){
String fileName = excelFile.getName();
fileInputStream = new FileInputStream(excelFile);
//放入壓縮zip包中;
zipOutputStream.putNextEntry(new ZipEntry(path + "/"+fileName));
//讀取檔案;
while((len=fileInputStream.read(buf)) >0){
zipOutputStream.write(buf, 0, len);
}
//關閉;
zipOutputStream.closeEntry();
if(fileInputStream != null){
fileInputStream.close();
}
}
}
/*if(zipOutputStream !=null){
zipOutputStream.close();
}*/
return zipOutputStream;
}
/**
* //壓縮檔案
* @param srcfile 要壓縮的檔案陣列
* @param zipfile 生成的zip檔案物件
*/
public static void ZipFiles(java.io.File[] srcfile, File zipfile) throws Exception {
byte[] buf = new byte[1024];
FileOutputStream fos = new FileOutputStream(zipfile);
ZipOutputStream out = new ZipOutputStream(fos);
for (int i = 0; i < srcfile.length; i++) {
FileInputStream in = new FileInputStream(srcfile[i]);
out.putNextEntry(new ZipEntry(srcfile[i].getName()));
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
}
out.close();
fos.flush();
fos.close();
}
/**
* 刪除資料夾及資料夾下所有檔案
* @param dir
* @return
*/
public static boolean deleteDir(File dir) {
if (dir == null || !dir.exists()){
return true;
}
if (dir.isDirectory()) {
String[] children = dir.list();
//遞迴刪除目錄中的子目錄下
for (int i=0; i<children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// 目錄此時為空,可以刪除
return dir.delete();
}
/**
* 生成html
* @param msg
* @return
* @author zgd
* @time 2018年6月25日11:47:07
*/
public static String getErrorHtml(String msg) {
StringBuffer sb = new StringBuffer();
sb.append("<html>");
sb.append("<head>");
sb.append("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
sb.append("</head>");
sb.append("<body>");
sb.append("<div id='errorInfo'> ");
sb.append("</div>");
sb.append("<script>alert('"+msg+"')</script>");
sb.append("</body>");
sb.append("</html>");
return sb.toString();
}
/**
* 設定下載excel的響應頭資訊
* @param response
* @param request
* @param agent
* @param fileName
* @throws IOException
* @author zgd
* @time 2018年6月25日11:47:07
*/
public static void setExcelHeadInfo(HttpServletResponse response, HttpServletRequest request, String fileName) {
try {
// 獲取客戶端瀏覽器的型別
String agent = request.getHeader("User-Agent");
// 對檔名重新編碼
String encodingFileName = FileDownloadUtils.encodeDownloadFilename(fileName, agent);
// 告訴客戶端允許斷點續傳多執行緒連線下載
response.setHeader("Accept-Ranges", "bytes");
//檔案字尾
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + encodingFileName);
} catch (IOException e) {
logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() +"發生的異常是: ",e);
throw new RuntimeException(e);
}
}
/**
* 設定下載zip的響應頭資訊
* @param response
* @param fileName 檔名
* @param request
* @throws IOException
* @author zgd
* @time 2018年6月25日11:47:07
*/
public static void setZipDownLoadHeadInfo(HttpServletResponse response, HttpServletRequest request, String fileName) throws IOException {
// 獲取客戶端瀏覽器的型別
String agent = request.getHeader("User-Agent");
response.setContentType("application/octet-stream ");
// 表示不能用瀏覽器直接開啟
response.setHeader("Connection", "close");
// 告訴客戶端允許斷點續傳多執行緒連線下載
response.setHeader("Accept-Ranges", "bytes");
// 對檔名重新編碼
String encodingFileName = FileDownloadUtils.encodeDownloadFilename(fileName, agent);
response.setHeader("Content-Disposition", "attachment; filename=" + encodingFileName);
}
}
1.控制層接收引數
/**
* 匯出商家訂單資料列表的excel檔案
*
* @param request
* @author zgd
* @time 2018年6月5日14:47:21
*/
@RequestMapping(value = "/exportStoreOrderList")
public void exportStoreOrderList(HttpServletRequest request, HttpServletResponse response) {
/*
* 從service層獲取List的資料,此處省略
*/
List<Map<String, Object>> list = data;
//匯出excel
// 建立Excel檔案,每個excel限制10000條資料,超過則打包zip
int size = 10000;
//每次快取1000條到記憶體,其餘寫到磁碟
int rowCache = 1000;
String fileName = "商家訂單資料-" + DateUtils.getDateWx();
// 建立Excel檔案,每個excel限制10000條資料,超過則打包zip
int size = 10000;
//每次快取1000條到記憶體,其餘寫到磁碟
int rowCache = 1000;
String fileName = "商家訂單資料-" + DateUtils.getDateWx();
//匯出excel
try {
//excel檔案個數
int n = list.size() / size + 1;
if (list != null) {
SXSSFWorkbook workbook = getStoreOrderExcel(list, rowCache);
if (n == 1) {
//下載單個excle
fileName = fileName + ".xls";
FileDownloadUtils.downloadExcel(request, response, fileName, workbook);
} else {
fileName = "批量" + fileName + ".zip";
String realPath = request.getSession().getServletContext().getRealPath("WEB-INF");
//建立臨時資料夾儲存excel
String tempDir = realPath + "/tempDir/" + DateUtils.getDateWx();
List<File> files = getStoreOrderExcels(tempDir, size, list, rowCache);
String zipPath = tempDir + "\\" + fileName;
//下載zip
FileDownloadUtils.downloadZip(request, response, fileName, files, zipPath);
//刪除tempDir資料夾和其中的excel和zip檔案
boolean b = FileDownloadUtils.deleteDir(new File(realPath + "\\tempDir"));
if (!b) {
throw new RuntimeException("tempDir資料夾及其中的臨時Excel和zip檔案刪除失敗");
}
}
}
}catch (Exception e) {
try {
if (!response.isCommitted()) {
response.setContentType("text/html;charset=utf-8");
response.setHeader("Content-Disposition", "");
String html = FileDownloadUtils.getErrorHtml("下載失敗");
response.getOutputStream().write(html.getBytes("UTF-8"));
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
2. getStoreOrderExcel方法解析
/**
* 將資料庫查出來的商家訂單資料,建立成excel
* @param list 商家訂單資料
* @param rowCache 緩衝的行數
* @return
*/
private SXSSFWorkbook getStoreOrderExcel(List<Map<String, Object>> list, int rowCache) {
//如果rowCache是1000,就是每次讀取1000條資料到快取中
SXSSFWorkbook workbook = new SXSSFWorkbook(rowCache);
/**
....設定行,列,塞入資料
**/
return workbook;
}
2.1 這裡分成兩條線: 下載excel和下載zip
2.2 下載excel支線
2.2.1 downloadExcel方法解析
/**
* 下載excel
*
* @param request
* @param response
* @param fileName
* @param workbook
* @throws Exception
* @author zgd
* @time 2018年6月25日11:47:07
*/
private void downloadExcel(HttpServletRequest request, HttpServletResponse response, String fileName, SXSSFWorkbook workbook) {
//一個流兩個頭
//設定下載excel的頭資訊
FileDownloadUtils.setExcelHeadInfo(response, request, fileName);
// 寫出檔案
ServletOutputStream os = null;
try {
os = response.getOutputStream();
workbook.write(os);
} catch (IOException e) {
logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + "發生的異常是: ", e);
throw new RuntimeException(e);
} finally {
try {
if (os != null) {
os.flush();
os.close();
}
if (workbook != null) {
workbook.close();
}
} catch (Exception e1) {
logger.error(Thread.currentThread().getStackTrace()[1].getMethodName() + "發生的異常是: ", e1);
throw new RuntimeException(e1);
}
}
}
下載excel支線完畢
2.3 下載zip支線
2.3.1 getStoreOrderExcels方法解析,先將大量資料轉化成批量的excel臨時檔案
/**
* 將資料轉成多個excel檔案放在專案中
*
* @param tempDir
* @param size 每個excel的資料的行數
* @param list 資料
* @param rowCache 下載時快取的行數
* @throws Exception
* @author zgd
* @time 2018年6月25日11:47:07
*/
private List<File> getStoreOrderExcels( String tempDir, int size, List<Map<String, Object>> list, int rowCache) throws Exception {
//excel檔案個數
int n = list.size() / size + 1;
FileDownloadUtils.createFile(tempDir);
List<File> files = new ArrayList<File>(); //宣告一個集合,用來存放多個Excel檔案路徑及名稱
for (int i = 0; i < n; i++) {
int max = Math.min((i + 1) * size, list.size());
//避免將需要合併的單元格拆分成兩個表 , 如果最後的一條主訂單資料等於下一條資料,max+1
while (max < list.size() - 1 && list.get(max).get("orderNo").equals(list.get(max + 1).get("orderNo"))) {
max++;
}
List<Map<String, Object>> partList = list.subList(i * size, max);
SXSSFWorkbook wb = getStoreOrderExcel(partList, rowCache);
//生成一個excel
String path = tempDir + "\\商家訂單資料-" + (i + 1) + ".xlsx";
generateExcelToPath(wb, path);
//excel新增到files中
files.add(new File(path));
}
return files;
}
2.3.1.1 generateExcelToPath方法,將生成excel到指定路徑
/**
* 生成excel到指定路徑
* @param wb
* @param path
* @throws Exception
*/
private void generateExcelToPath(SXSSFWorkbook wb, String path) throws Exception {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(path);
wb.write(fos);
} finally {
if (fos != null) {
fos.flush();
fos.close();
}
if (wb != null) {
wb.close();
}
}
}
2.3.2 downloadZip方法解析,打包下載zip
/**
* 將批量檔案打包下載成zip
* @param request
* @param response
* @param zipName 下載的zip名
* @param files 要打包的批量檔案
* @param zipPath 生成的zip路徑
* @throws Exception
*/
private void downloadZip(HttpServletRequest request, HttpServletResponse response, String zipName, List<File> files, String zipPath)throws Exception {
File srcfile[] = new File[files.size()];
File zip = new File(zipPath);
for (int i = 0; i < files.size(); i++) {
srcfile[i] = files.get(i);
}
//生成.zip檔案;
FileInputStream inStream = null;
ServletOutputStream os = null;
try {
//設定下載zip的頭資訊
FileDownloadUtils.setZipDownLoadHeadInfo(response, request, zipName);
os = response.getOutputStream();
FileDownloadUtils.ZipFiles(srcfile, zip);
inStream = new FileInputStream(zip);
byte[] buf = new byte[4096];
int readLength;
while (((readLength = inStream.read(buf)) != -1)) {
os.write(buf, 0, readLength);
}
} finally {
if (inStream != null) {
inStream.close();
}
if (os != null) {
os.flush();
os.close();
}
}
}
zip下載支線完畢
3.附 打成zip包
直接用工具類
/**
* //壓縮檔案
* @param srcfile 要壓縮的檔案陣列
* @param zipfile 生成的zip檔案物件
*/
public static void ZipFiles(java.io.File[] srcfile, File zipfile) throws Exception {
byte[] buf = new byte[1024];
FileOutputStream fos = new FileOutputStream(zipfile);
ZipOutputStream out = new ZipOutputStream(fos);
for (int i = 0; i < srcfile.length; i++) {
FileInputStream in = new FileInputStream(srcfile[i]);
out.putNextEntry(new ZipEntry(srcfile[i].getName()));
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.closeEntry();
in.close();
}
out.close();
fos.flush();
fos.close();
}
4. 刪除臨時資料夾
/**
* 刪除資料夾及資料夾下所有檔案
* @param dir
* @return
*/
public static boolean deleteDir(File dir) {
if (dir == null || !dir.exists()){
return true;
}
if (dir.isDirectory()) {
String[] children = dir.list();
//遞迴刪除目錄中的子目錄下
for (int i=0; i<children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// 目錄此時為空,可以刪除
return dir.delete();
}