將web頁面上顯示的報表導出到excel文件裏是一種很常見的需求。然而,當數據量較大的狀況下,excel自己的支持最多65535行數據的問題便凸顯出來。下面就給出大數據量導出到excel的解決方 案。web
首先,對於數據超過了65535行的問題,很天然的就會想到將整個數據分塊,利用excel的多sheet頁的功能,將超出65535行後的數據寫入到下一個sheet頁中,即經過多sheet頁的方式,突破了最高65535行數據的限定。bash
具體作法就是,單獨作一個連接,使用JSP導出,在JSP上經過程序判斷報表行數,超過65535行後分SHEET寫入。這樣這個問題就得以解決了。服務器
更進一步地說,在這種大數據量的報表生成和導出中,要佔用大量的內存,尤爲是在使用TOMCAT的狀況下,JVM最高只能支持到2G內存,則會發生 內存溢出的狀況。此時的內存開銷主要是兩部分,一部分是該報表生成時的開銷,另外一部分是該報表生成後寫入一個EXCEL時的開銷。因爲JVM的GC機制是 不能強制回收的,所以,對於此種情形,咱們給出一個變通的解決方案。多線程
首先,將該報表設置起始行和結束行參數,在API生成報表的過程當中,分步計算報表(主要性能花費在查詢生成報表中),好比一張20萬行數據的報表,在生成過程當中,可經過起始行和結束 行分4-5次進行。這樣,就下降了報表生成時的內存佔用,在後面報表生成的過程當中,若是發現內存不夠,便可自動啓動JVM的GC機制,回收前面報表的緩 存。併發
導出EXCEL的過程,放在每段生成報表以後當即進行,改多個SHEET頁爲多個EXCEL,即在分步生成報表的同時分步生成EXCEL,則經過 POI包生成EXCEL的內存消耗也得以下降。經過屢次生成,一樣能夠在後面EXCEL生成所須要的內存不足時,有效回收前面生成EXCEL時佔用的內 存。app
再使用文件操做,對每一個客戶端的導出請求在服務器端根據SESSIONID和登錄時間生成惟一的臨時目錄,用來放置所生成的多個EXCEL,而後調 用系統控制檯,打包多個EXCEL爲RAR或者JAR方式,最終反饋給用戶一個RAR包或者JAR包,響應客戶請求後,再次調用控制檯刪除該臨時目錄。ide
使用這種方法,首先是經過分段運算和生成,有效下降了報表從生成結果到生成EXCEL的內存開銷。其次是經過使用壓縮包,響應給用戶的生成文件體積 大大縮小,下降了多用戶併發訪問時服務器下載文件的負擔,有效減小多個用戶導出下載時服務器端的流量,從而達到進一步減輕服務器負載的效果。oop
final int numOfCpuCores = Runtime.getRuntime().availableProcessors();
final double blockingCoefficient = 0.9;// 阻尼係數
final int maximumPoolSize = (int)(numOfCpuCores / (1 - blockingCoefficient));
ExecutorService threadPool = new ThreadPoolExecutor(numOfCpuCores,
maximumPoolSize,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue <Runnable>(),
Executors.privilegedThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
複製代碼
// 1.這裏每一個Excel放6萬條數據(分6個sheet頁,每一個1萬條),當數據量超過6萬條時,數據採用分段查詢
// 傳遞(起始行,結束行)參數,分段查詢,即分步生成報表的同時分步生成EXCEL
int SINGLE_EXCEPORT_EXCEL_MAX_NUM = 60000;
int count = bo.getTotalRecord();
final String fileNameWithTimestamp = fileName + "_" + DateUtil.getNowDateminStr();
if (count > SINGLE_EXCEPORT_EXCEL_MAX_NUM ) {
int excelCount = count / SINGLE_EXCEPORT_EXCEL_MAX_NUM +
(count % SINGLE_EXCEPORT_EXCEL_MAX_NUM != 0 ? 1 : 0);
final CountDownLatch latch = new CountDownLatch(excelCount);
final Long userId = user.getUserId();
for(int i = 1; i <= excelCount; i++){
bo.setPageNo(i);
bo.setPageSize(SINGLE_EXCEPORT_EXCEL_MAX_NUM);
final ParkRecordQryBO itemBo = new ParkRecordQryBO(bo);
final int index = i;
// 取一線程執行本次查詢
threadPool.execute(new Runnable(){
@Override
public void run() {
Page page = service.getParkRecord(itemBo);
List<ParkRecordQryBO> records = page.getResults();
try {
// 2.生成單個excel
ExportExcelUtil.createOneExcel(fileNameWithTimestamp, index ,
expRowsList, records, userId);
} catch (Exception e) {
e.printStackTrace();
}
latch.countDown();
}
});
}
// 3.壓縮excel文件並導出
latch.await();
ExportExcelUtil.createZipExport(request, response, fileNameWithTimestamp, userId);
複製代碼
/**
* @Description: 生成一個Excel存放到本地路徑
* @param fileNameWithTimestamp
* @param index
* @param excelHeader
* @param dataList
* @param <T>
* @param userId
*/
public static <T> void createOneExcel(final String fileNameWithTimestamp,
int index,
final String[] excelHeader,
final List<T> dataList,
Long userId ) {
final String localRelativePath = "" + userId + "/"+ fileNameWithTimestamp ;
Workbook wb = null;
FileOutputStream fos = null;
try {
// 建立一個Workbook,對應一個Excel文件
wb = writeExcel(dataList, excelHeader);
// 生成本地Excel初始文件
Map<String, Object> fileInfo = new HashMap<String, Object>();
FileUtil.createFile(localRelativePath, fileNameWithTimestamp +
"_" + index + "_.xls", fileInfo);
fos = new FileOutputStream(fileInfo.get("realPath").toString() );
wb.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (wb != null)
wb.close();
if (fos != null)
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
複製代碼
/**
* @param request
* @param response
* @param fileNameWithTimestamp
* @param userId
*/
public static void createZipExport(HttpServletRequest request,
HttpServletResponse response,
final String fileNameWithTimestamp,
Long userId) throws Exception{
final String localRelativePath = "" + userId + "/"+ fileNameWithTimestamp;
// 建立文件夾,先將生成的excel保存到服務器本地目錄
// excel文件路徑:'/app/file/[userId]/[fileNameWithTimestamp]/[fileNameWithTimestamp_i].xlS'
String excelFold = FileUtil.getFileRootPath() + localRelativePath;
// zip文件所在路徑:"/app/file/userId/fileNameWithTimestamp.zip"
String zipFold = FileUtil.getFileRootPath() + userId;
// 生成zip文件
final String zipFileName = fileNameWithTimestamp +".zip";
FileUtil.createZipFile(excelFold, zipFold, zipFileName);
// 建立導出輸入流
InputStream is = null;
try{
is = new FileInputStream(new File(zipFold + File.separator + zipFileName));
} catch(IOException e){
e.printStackTrace();
}
BufferedInputStream bis = new BufferedInputStream(is);
// ServletOutputStream out = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
// 解決設置名稱時的亂碼問題
String zipName = handleFileName(request, zipFileName);
// 設置response參數,能夠打開下載頁面
response.reset();
response.setContentType("application/vnd.ms-excel;charset=utf-8");
response.setHeader("Content-Disposition", "attachment;filename=" + zipName);
byte[] buff = new byte[2048];
int bytesRead;
// Simple read/write loop.
while ((bytesRead = bis.read(buff, 0, buff.length)) != -1 ) {
bos.write(buff, 0, bytesRead);
}
bis.close();
bos.close();
// 刪除用來臨時保存Excel的文件夾及zip文件
FileUtil.deleteDir(new File(zipFold));
}
複製代碼