1.SXSSFWorkbook理解:
SXSSFWorkbook是用來生成海量excel數據文件,主要原理是藉助臨時存儲空間生成excel,SXSSFWorkbook專門處理大數據,對於大型excel的建立且不會內存溢出的,就只有SXSSFWorkbook了。它的原理很簡單,用硬盤空間換內存(就像hashmap用空間換時間同樣)。 SXSSFWorkbook是streaming版本的XSSFWorkbook,它只會保存最新的excel rows在內存裏供查看,在此以前的excel rows都會被寫入到硬盤裏(Windows電腦的話,是寫入到C盤根目錄下的temp文件夾)。被寫入到硬盤裏的rows是不可見的/不可訪問的。只有還保存在內存裏的才能夠被訪問到。
注:HSSFWorkbook和XSSFWorkbook的Excel Sheet導出條數上限(<=2003版)是65535行、256列,(>=2007版)是1048576行,16384列,若是數據量超過了此上限,那麼能夠使用SXSSFWorkbook來導出。實際上上萬條數據,甚至上千條數據就能夠考慮使用SXSSFWorkbook了。
注意:首先須要引入依賴:注意:4.0.0版本的JDK須要1.8以上,若是JDK是1.7的,那麼就使用3.9版本的依賴java
2.數據過多使用SXSSFWorkbook也是會出現內存溢出的問題,主要出現的兩個地方:
a.從數據庫讀取數據到內存時溢出。
優化角度1:取數據時用分頁的方法分批取數據,而後寫入sheet中。這樣就能夠避免取數據時內存溢出;
java.lang.OutOfMemoryError:GC overhead limit exceeded
b.FileOutputStream os = new FileOutputStream(path); wb.write(os);
優化角度2: 建立Workbook時設置工做簿保存在內存中數據的條數,這樣一旦這個Workbook中數據量超過1000就會寫入到磁盤中,減小內存的使用量來提升速度和避免溢出。
Workbook wb = new SXSSFWorkbook(1000);數據庫
3.經過分頁查詢實現海量excle數據的導出,避免發生OOM。代碼以下:apache
@RequestMapping("/exportPurchaseInfo.do") @ResponseBody public void exportPurchaseGoodsInfo(HttpServletRequest request, HttpServletResponse response,QueryParamDto reqDto) { try { int pageSize = 1000;//分頁的大小,即每次分頁查詢多少條記錄開關 String[] headers = {"序號","商品編號","商品名稱","品類","品牌","採購金額(元)","採購數量(件)"}; SXSSFWorkbook workbook = ExportExcelByPageUtil.makeSXSSFWorkbook(purchseReportExportExcelByPageService,reqDto, headers, pageSize); ExportExcelByPageUtil.exportExcel(request, response, workbook, "商品銷售報表.xlsx"); } catch (Exception e) { logger.error("導出商品銷售報表異常",e); } }
ExportExcelByPageUtil.javaapp
public static <T> SXSSFWorkbook makeSXSSFWorkbook(ReportExportExcelByPageService<T> exportExcelByPageTyService, T queryDataBo, String[] headers, int pageSize){ int rowAccessWindowSize = 100; //這樣表示SXSSFWorkbook只會保留100條數據在內存中,其它的數據都會寫到磁盤裏,這樣的話佔用的內存就會不多 SXSSFWorkbook workbook = new SXSSFWorkbook(rowAccessWindowSize); SXSSFSheet sheet = (SXSSFSheet) workbook.createSheet("sheet1"); Map<String,CellStyle> cellStyles=getCellStyle(workbook); CellStyle cs = cellStyles.get("cs"); CellStyle cs2 = cellStyles.get("cs2"); //設置列名 Row row = sheet.createRow((short) 0); for (int i = 0; i < headers.length; i++) { Cell cell = row.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(cs); } String pattern = "yyyy-MM-dd HH:mm:ss"; String beginTime = DateUtil.formatDate(new Date(), pattern); int allCount = exportExcelByPageTyService.queryAllCount(queryDataBo); if(allCount <= 0){ return workbook; } //處理數據量超過一個sheet頁最大量時異常,支持的最大導出數據量爲10000 int maxExportCount = 10000; allCount = allCount > maxExportCount ? maxExportCount : allCount; //page:查詢總數據量爲 allCount條記錄時,每次生成pageSize條記錄 須要page 次進行導出 int page = (int) Math.ceil((double) allCount / (double) pageSize); //101條記錄 aliquotFlag=false boolean aliquotFlag = allCount % pageSize == 0; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); for(int i = 0; i < page; i++){ logger.error("第"+(i+1)+"次循環開始:"+ DateUtil.formatDate(new Date(), pattern)); int startIndex = i*pageSize; int maxCount = pageSize; //最後一次的最大記錄數 if(!aliquotFlag && i==(page-1)){ maxCount = allCount - startIndex; } logger.error("導出分頁:startIndex==>"+startIndex+", pageSize==>"+maxCount); List<String> ids = exportExcelByPageTyService.queryIdsInfo(queryDataBo, startIndex, maxCount); List<LinkedHashMap<String, Object>> dataList = exportExcelByPageTyService.queryDatas(ids); if(CollectionUtils.isNotEmpty(dataList)){ Set<Map.Entry<String, Object>> set = null; Iterator<Map.Entry<String, Object>> iterator = null; int j = 0; for (int rowNum = 1, len = dataList.size() + 1; rowNum < len; rowNum++) { Row row1 = sheet.createRow(startIndex+rowNum); set = dataList.get(rowNum - 1).entrySet(); iterator = set.iterator(); j = 0; while (iterator.hasNext()) { Map.Entry<String, Object> entry = (Map.Entry<String, Object>) iterator.next(); String cellValue = ""; if(entry.getValue()==null){ cellValue = ""; }else if(entry.getValue() instanceof Timestamp){ cellValue = sdf.format((Timestamp) entry.getValue()); } else { cellValue = String.valueOf(entry.getValue()); } Cell cell = row1.createCell(j); cell.setCellValue(cellValue); cell.setCellStyle(cs2); j++; } if (rowNum % rowAccessWindowSize == 0) { try { sheet.flushRows(); } catch (IOException e) { logger.error("sheet從內存寫入本地硬盤失敗", e); } } } } dataList = null; logger.error("第"+(i+1)+"次循環結束:"+formatDate(new Date(), pattern)); } logger.error("導出分頁開始:"+beginTime); logger.error("導出分頁結束:"+ formatDate(new Date(), pattern)); return workbook; }
public static boolean exportExcel(HttpServletRequest request, HttpServletResponse response,SXSSFWorkbook workbook, String fileName){ OutputStream out = null; try { String userAgent = request.getHeader("USER-AGENT"); fileName = dealChineseFileName(userAgent, fileName); out = response.getOutputStream(); response.setContentType("application/x-download"); response.setHeader("Pragma", "public"); response.setHeader("Cache-Control", "max-age=30"); response.setHeader("Content-disposition", "attachment;filename=" + fileName); workbook.write(out); out.flush(); } catch (Exception e) { logger.error("導出Excel文件失敗", e); return false; }finally { if (out != null) { try { out.close(); } catch (IOException e) { logger.error("導出Excel文件關閉輸出流失敗", e); } finally { out = null; } } if(workbook!=null){ try { workbook.dispose(); workbook.close(); } catch (IOException e) { logger.error("導出Excel文件關閉輸出流失敗", e); } finally { workbook = null; } } } return true; }
ReportExportExcelByPageService.javaide
public interface ReportExportExcelByPageService<T> { /** 查知足條件記錄數 */ @Method(description = "查知足條件count") public int queryAllCount(T queryDataBo); /** 分頁查知足條件Ids */ @Method(idempotent = true, retryTimes = 3, timeout = 30000, description = "分頁查知足條件Ids") public List<String> queryIdsInfo(T queryDataBo, int startIndex, int pageSize); /** 根據Ids查數據 */ @Method(idempotent = true, retryTimes = 3, timeout = 30000, description = "根據Ids查數據") public List<LinkedHashMap<String, Object>> queryDatas(List<String> ids); }
ReportExportExcelByPageServiceImpl.java字體
public class ReportExportExcelByPageServiceImpl implements ReportExportExcelByPageService<QueryParamDto> { protected Logger logger = LoggerFactory.getLogger(this.getClass()); @Override public int queryAllCount(QueryParamDto queryDataBo) { //查詢記錄數業務代碼 } @Override public List<String> queryIdsInfo(QueryParamDto queryDataBo, int startIndex, int pageSize) { List<String> list = new ArrayList<String>(); list.add(queryDataBo.getBeginTime()); list.add(queryDataBo.getEndTime()); list.add(queryDataBo.getUserCode()); list.add(queryDataBo.getType()); list.add(queryDataBo.getAllType()); list.add(String.valueOf(startIndex)); list.add(String.valueOf(pageSize)); return list; } @Override public List<LinkedHashMap<String, Object>> queryDatas(final List<String> ids) { List<LinkedHashMap<String, Object>> result = new ArrayList<>(); if (CollectionUtils.isEmpty(ids)) { return result; } QueryParamDto queryDataBo=new QueryParamDto(); queryDataBo.setBeginTime( ids.get(0)); queryDataBo.setEndTime(ids.get(1)); queryDataBo.setUserCode(ids.get(2)); queryDataBo.setType(ids.get(3)); queryDataBo.setAllType(ids.get(4)); queryDataBo.setStartIndex(Integer.parseInt(ids.get(5))); queryDataBo.setPageSize(Integer.parseInt(ids.get(6))); //根據分頁的入參進行分頁查詢:返回結果爲list List<QueryResultData> commodityListInfo = ...; /* SELECT zp.commodity_code commodityCode, zp.commodity_name commodityName, zp.category_name categoryName, zp.brand brand, sum(zp.purchase_money) purchaseMoney, sum(zp.purchase_num) purchaseNum FROM tableName zp WHERE zp.purchase_day BETWEEN :beginTime AND :endTime and user_code =:userCode group by zp.commodity_code order by purchaseMoney desc <#if startIndex !=null && startIndex !="" && pageSize!=null && pageSize !=""> LIMIT :startIndex ,:pageSize </#if> */ //排名","商品編號","商品名稱","品類","品牌","採購金額(元)","採購數量(件) if(org.apache.commons.collections.CollectionUtils.isNotEmpty(commodityListInfo)){ for (int i = 0; i < commodityListInfo.size(); i++) { LinkedHashMap map = new LinkedHashMap(); QueryResultData queryResultData = commodityListInfo.get(i); map.put("xuHao", (i+1) + ""); map.put("commodityCode", queryResultData.getCommodityCode()); map.put("commodityName", queryResultData.getCommodityName()); map.put("categoryName", queryResultData.getCategoryName()); map.put("brand", queryResultData.getBrand()); map.put("purchaseMoney", queryResultData.getPurchaseMoney()); map.put("purchaseNum", queryResultData.getPurchaseNum()); result.add(map); } } return result; } }
DateUtil.java大數據
import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class DateUtil { public static final String YYYYMMDD = "yyyy-MM-dd"; public static final String YYYYMMDDHHMMSS = "yyyy-MM-dd HH:mm:ss"; public static final String YYYYMMDDHHmmss = "yyyy-MM-dd HH:mm:ss"; private DateUtil() { } public static Date parseDate(String time, String pattern) { if (time == null) { return null; } else { SimpleDateFormat df = new SimpleDateFormat(pattern); try { return df.parse(time); } catch (ParseException var4) { return null; } } } public static String formatDate(Date time, String pattern) { if (time == null) { return ""; } else { String result = null; DateFormat df = new SimpleDateFormat(pattern); result = df.format(time); return result; } } }
getCellStyle方法優化
public static Map<String,CellStyle> getCellStyle(SXSSFWorkbook workbook){ Map<String,CellStyle> cellStyles=new HashMap<String,CellStyle>(); // 建立兩種單元格格式 CellStyle cs = workbook.createCellStyle(); CellStyle cs2 = workbook.createCellStyle(); // 建立兩種字體 Font f = workbook.createFont(); Font f2 = workbook.createFont(); // 建立第一種字體樣式(用於列名) f.setFontHeightInPoints((short) 10); f.setColor(IndexedColors.BLACK.getIndex()); f.setBoldweight(Font.BOLDWEIGHT_BOLD); // 建立第二種字體樣式(用於值) f2.setFontHeightInPoints((short) 10); f2.setColor(IndexedColors.BLACK.getIndex()); // 設置第一種單元格的樣式(用於列名) cs.setFont(f); cs.setBorderLeft(CellStyle.BORDER_THIN); cs.setBorderRight(CellStyle.BORDER_THIN); cs.setBorderTop(CellStyle.BORDER_THIN); cs.setBorderBottom(CellStyle.BORDER_THIN); cs.setAlignment(CellStyle.ALIGN_CENTER); // 設置第二種單元格的樣式(用於值) cs2.setFont(f2); cs2.setBorderLeft(CellStyle.BORDER_THIN); cs2.setBorderRight(CellStyle.BORDER_THIN); cs2.setBorderTop(CellStyle.BORDER_THIN); cs2.setBorderBottom(CellStyle.BORDER_THIN); cs2.setAlignment(CellStyle.ALIGN_CENTER); cellStyles.put("cs",cs); cellStyles.put("cs2",cs2); return cellStyles; }