7、SXSSFWorkbook生成大excle,避免內存溢出

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

}
View Code

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

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);
}
View Code

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

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

 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;
    }
View Code
相關文章
相關標籤/搜索