poi實現百萬級數據導出


 

注意使用 SXSSFWorkbook 此類在構造表格和處理行高的時候效率極高,剛開始時我使用的 XSSFWorkbook 就出現構造表格效率極低,一萬行基本需要3秒左右,那當導出百萬級數據就慢的要死啦,而且他會讓內存溢出

 

POI3.8的SXSSF包是XSSF的一個擴展版本,支持流處理,在生成大數據量的電子表格且堆空間有限時使用。SXSSF通過限制內存中可訪問的記錄行數來實現其低內存利用,當達到限定值時,新一行數據的加入會引起老一行的數據刷新到硬盤。

       比如內存中限制行數為100,當行號到達101時,行號為0的記錄刷新到硬盤並從內存中刪除,當行號到達102時,行號為1的記錄刷新到硬盤,並從內存中刪除,以此類推。

       rowAccessWindowSize代表指定的內存中緩存記錄數,默認為100,此值可以通過

new SXSSFWorkbook(int rowAccessWindowSize)或SXSSFSheet.setRandomAccessWindowSize(intwindowSize)來設置。

       SXSSF在把內存數據刷新到硬盤時,是把每個SHEET生成一個臨時文件,這個臨時文件可能會很大,有可以會達到G級別,如果文件的過大對你來說是一個問題,你可以使用下面的方法讓SXSSF來進行壓縮,當然性能也會有一定的影響。

 

               SXSSFWorkbook wb = new SXSSFWorkbook();               wb.setCompressTempFiles(true);

臨時文件: 

SXSSF在導出的過程中會生成兩種臨時文件,一種是為每個sheet頁生成一個 xml 臨時文件,一種是最終導出時生成的完整.xlsx 文件

臨時文件所在位置:

windows: C盤下的 AppData\Local\Temp\poifiles    例如我的:C:\Users\011336\AppData\Local\Temp\poifiles

Linux:在Tomcat目錄下的 temp/poifiles 

臨時文件的刪除:

workbook.write(fileOut);     write()方法中包含刪除 .xlsx 文件的方法,在它的finally代碼塊里,具體可以去查看源碼

workbook.dispose();          dispose()方法就是用來刪除那些 xml 格式的臨時文件的

需要注意的細節:

 每創建完一個sheet頁就會生成一個xml文件  但是所有的 xml 文件都是空的,只有調用workbook.write(fileOut); 方法時,才會往xml中寫數據,也就是說之前構造的幾百萬數據都在內存中,這是很危險的行為,當達到一定量時可能就會有內存溢出的風險,所以要記得在每個sheet頁構造完成之后都手動把數據刷到磁盤當中((SXSSFSheet)sheet).flushRows();其實write()方法中也是for循環調用的flushRows()方法。

最關鍵的點:

  記得點贊哦...

1.  CommentController  

/**
     * excel導出功能
     * @param commentSearch
     * @param response
     * @param request
     * @return
     * @throws Exception
     */
    @RequestMapping("/exportCommentInfo")
    @ResponseBody
    @NoRepeatRequest
    public BaseDTO exportCommentInfo(CommentSearch commentSearch, HttpServletResponse response, HttpServletRequest request) throws Exception{
        LOGGER.info("CommentController.exportCommentInfo start");
        long startTime = System.currentTimeMillis();
        LOGGER.info("開始下載.........................................");
        List<ErrorInfo> errors = null;
        int result = 0;
        String fileName = FileNameUtils.getExportCommontExcelFileName();
        OutputStream fileOut  = null;
        SXSSFWorkbook workbook = null;try {
            LOGGER.debug("classpath: " + fileName);
            workbook = new SXSSFWorkbook(10000);//內存中實時存在10000個對象,超過的實時寫入磁盤,保證內存消耗不會過大
            commentService.exportCommentInfo(request,workbook, commentSearch);
            // 定義excel文件名
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Content-Disposition", "attachment; filename=\""
                    + URLEncoder.encode(fileName, "UTF-8") + "\"");

        // 定義輸出流
        fileOut = response.getOutputStream();
        // 調用導出方法生成最終的 poi-sxssf-template.xlsx 臨時文件,並且此方法包含刪除此臨時文件的方法
        workbook.write(fileOut);
        // 此方法能夠刪除導出過程中生成的xml臨時文件
        workbook.dispose();

        } catch (Exception e) {
            LOGGER.error("InterfaceInfoController.exportInterfaceInfo Exception: ", e);
            ErrorInfo errorInfo = new ErrorInfo("system.error", "系統異常!");
            errors = Arrays.asList(errorInfo);
        }finally {
            workbook.close();
        }
        LOGGER.info("下載完成....|||||.......用時:" + (System.currentTimeMillis() - startTime));
        return tranferBaseDTO(errors, result);
    }

2.導出實現類  exportCommentInfo

/**
     * excel 導出
     * @param: [request, workbook, commentSearch]
     * @return: void
     * @auther: 011336
     * @date: 2018/12/7 15:03
     */
    @Override
    public void exportCommentInfo(HttpServletRequest request, SXSSFWorkbook workbook, CommentSearch commentSearch) {
        //excel樣式
        CellStyle centerStyle = workbook.createCellStyle();
        CellStyle cellStyleCenter = workbook.createCellStyle();
        CellStyle cellStyleLeft = workbook.createCellStyle();

        cellStyleCenter.setAlignment(HSSFCellStyle.ALIGN_CENTER); //水平布局:居中
        cellStyleCenter.setVerticalAlignment(CellStyle.VERTICAL_CENTER);//垂直居中
        cellStyleCenter.setWrapText(true);
        cellStyleLeft.setAlignment(HSSFCellStyle.ALIGN_LEFT); //水平布局:居左
        cellStyleLeft.setVerticalAlignment(CellStyle.VERTICAL_CENTER);//垂直居中
        cellStyleLeft.setWrapText(true); 
        Font font =workbook.createFont();
        font.setColor(Font.COLOR_NORMAL); //字體顏色
        font.setFontName("黑體"); //字體
        font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); //寬度

        // 設置標題單元格類型
        centerStyle.setFont(font);
        centerStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); //水平布局:居中
        centerStyle.setWrapText(true);
        centerStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER);
        centerStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);//設置前景填充樣式
        centerStyle.setFillForegroundColor(HSSFColor.GREY_50_PERCENT.index);//前景填充色
        CreationHelper createHelper = workbook.getCreationHelper();
        // 設置標題邊框
        centerStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);
        centerStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);
        centerStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);
        centerStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);

        //分批導出
        int totalCount = countCommentNum(commentSearch);
        //如果導出數據量大於   設定的最大數據量    則最多不能超過設定的數量
        if(pageNumExport != null && totalCount > pageNumExport*limitExport){
                totalCount = pageNumExport*limitExport;
        }
 int number = (totalCount % limitExport) == 0 ? totalCount
                / limitExport : totalCount / limitExport + 1;
List
<CommentVo> commentVOs = new ArrayList<>();
for (int i = 0; i < number; i++) {long startTime = System.currentTimeMillis(); LOGGER.info("【第"+i+"】次開始查詢數據庫........................................."); commentVOs = getCommentVoExport(commentSearch, i*limitExport, limitExport); LOGGER.info("【第"+i+"】次數據庫查詢完成....|||||.......用時:" + (System.currentTimeMillis() - startTime)); int page = i+1; Sheet sheet = workbook.createSheet("評論清單"+( page<10 ? "0"+page : page ) );
batchExport(request,sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft);

        try {
          ((SXSSFSheet)sheet).flushRows();//每創建完成一個sheet頁就把數據刷新到磁盤
        } catch (IOException e) {
          LOGGER.error("CommentServiceImpl.exportCommentInfo flushRows() exception ,that is not important"+e);
          e.printStackTrace();
        }

commentVOs.clear(); } }
-----提示:如果這里不手動刷的話,當所有sheet頁都刷到內存中后,workbook.write()方法會采用for循環把所有的數據都刷到磁盤中,也就是說,如果這里不手動刷,那么所有的對象就都在內存中。
實測表明 即使它已經創建的xml臨時文件,此時所有的xml臨時文件也是空的,都只能通過最后的
workbook.write()方法統一刷新到磁盤。那么就可能會有內存溢出的風險
 
        
3.   batchExport(request,sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft); 的實現
/**
     * 構造excel,賦值,樣式
     * @param: [request, sheet, commentSearch, commentVOs, centerStyle, cellStyleCenter, cellStyleLeft]
     * @return: void
     * @auther: 011336
     * @date: 2018/12/7 15:20
     */
    public void batchExport(HttpServletRequest request,Sheet sheet , CommentSearch commentSearch,List<CommentVo> commentVOs,
                            CellStyle centerStyle,CellStyle cellStyleCenter,CellStyle cellStyleLeft){
        if (CollectionUtils.isEmpty(commentVOs)) {
            LOGGER.debug("exportCommentInfo finish: " + commentVOs);
        }

        Row newRowOfInparamTitle = sheet.createRow(0);
        
        String[] headerOfInParam = { "序號", "評價日期", "來源", "星級", "評論內容", "類型一級", "類型二級", "類型三級", "情感識別","建議類","訂單號", "航班號", "航班日期","訂單聯系人","聯系電話","備注"};
        for (int j = 0; j < headerOfInParam.length; j++) {
            newRowOfInparamTitle.createCell(j);
        }
        for (int j = 0; j < headerOfInParam.length; j++) {
            sheet.getRow(0).getCell(j).setCellValue(new XSSFRichTextString(headerOfInParam[j]));
            sheet.getRow(0).getCell(j).setCellStyle(centerStyle);
        }
        long startTime = System.currentTimeMillis();
        LOGGER.info("構造表格開始.........................................");
        for (int i = 0; i < commentVOs.size(); i++) {
            CommentVo commentVo = commentVOs.get(i);
            int index = i + 1;
            Row createRow = sheet.createRow(i + 1);
            for (int j = 0; j < headerOfInParam.length; j++) {
                createRow.createCell(j);
            }
            sheet.getRow(i + 1).getCell(0).setCellValue(index + "");
            sheet.getRow(i + 1).getCell(0).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(1).setCellValue(dealTrim(dateToStr2(commentVo.getCmtTime())));
            sheet.getRow(i + 1).getCell(1).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(2).setCellValue(dealTrim(commentVo.getTerminalId()));
            sheet.getRow(i + 1).getCell(2).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(3).setCellValue(dealTrim(commentVo.getCmtLevel()));
            sheet.getRow(i + 1).getCell(3).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(4).setCellValue(dealTrim(commentVo.getCmtText()));
            sheet.getRow(i + 1).getCell(4).setCellStyle(cellStyleLeft);
            sheet.getRow(i + 1).getCell(5).setCellValue(dealTrim(dealTrim(commentVo.getClassfyFirst())));
            sheet.getRow(i + 1).getCell(5).setCellStyle(cellStyleLeft);
            sheet.getRow(i + 1).getCell(6).setCellValue(dealTrim(commentVo.getClassfySecond()));
            sheet.getRow(i + 1).getCell(6).setCellStyle(cellStyleLeft);
            sheet.getRow(i + 1).getCell(7).setCellValue(dealTrim(commentVo.getClassfyThird()));
            sheet.getRow(i + 1).getCell(7).setCellStyle(cellStyleLeft);
            String emotion = commentVo.getEmotion();
            if("0".equals(emotion)){
                emotion="差評";
            }else if("1".equals(emotion)){
                emotion="好評";
            }
            sheet.getRow(i + 1).getCell(8).setCellValue(dealTrim(emotion));
            sheet.getRow(i + 1).getCell(8).setCellStyle(cellStyleCenter);
            String isSuggestion = commentVo.getIsSuggestion();
            if("0".equals(isSuggestion)){
                isSuggestion="否";
            }else if("1".equals(isSuggestion)){
                isSuggestion="是";
            }
            sheet.getRow(i + 1).getCell(9).setCellValue(dealTrim(isSuggestion));
            sheet.getRow(i + 1).getCell(9).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(10).setCellValue(dealTrim(commentVo.getOrderNo()));
            sheet.getRow(i + 1).getCell(10).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(11).setCellValue(dealTrim(commentVo.getFlightNo()));
            sheet.getRow(i + 1).getCell(11).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(12).setCellValue(dateToStr(commentVo.getFlightDate()));
            sheet.getRow(i + 1).getCell(12).setCellStyle(cellStyleCenter);
            sheet.getRow(i + 1).getCell(13).setCellValue(dealTrim(commentVo.getcName()));
            sheet.getRow(i + 1).getCell(13).setCellStyle(cellStyleLeft);
            sheet.getRow(i + 1).getCell(14).setCellValue(dealTrim(commentVo.getcTel()));
            sheet.getRow(i + 1).getCell(14).setCellStyle(cellStyleLeft);
            sheet.getRow(i + 1).getCell(15).setCellValue(dealTrim(commentVo.getRemark()));
            sheet.getRow(i + 1).getCell(15).setCellStyle(cellStyleLeft);
            if(i%1000 == 0){
                request.getSession().setAttribute("currentNum",i+1);
            }
        }
        LOGGER.info("構造表格結束....|||||.......用時:" + (System.currentTimeMillis() - startTime));

        long startTime2 = System.currentTimeMillis();
        LOGGER.info("處理行高開始.........................................");
        dealColumWidth( headerOfInParam, sheet,commentVOs, request);
        LOGGER.info("處理行高結束....|||||.......用時:" + (System.currentTimeMillis() - startTime2));
    }

4.  處理行高代碼的實現   dealColumWidth( headerOfInParam, sheet,commentVOs, request);這里主要是設置固定列寬,然后自適應行高,二期自適應行高他是不提供方法的,只能自己去計算

 /**
     * 設置列寬
     * @param: [headerOfInParam, sheet, commentVOs, request]
     * @return: void
     * @auther: 011336
     * @date: 2018/12/7 15:20
     */
    public void dealColumWidth(String[] headerOfInParam,Sheet sheet,List<CommentVo> commentVOs,HttpServletRequest request){
        //單獨處理   評論內容  和  評論備注  以及 分類   的寬度 和高度
        sheet.setColumnWidth(1, 5500);//11個漢字  評價日期
        sheet.setColumnWidth(2, 1500);//3個漢字   來源
        sheet.setColumnWidth(3, 1500);//3個漢字   星級
        sheet.setColumnWidth(4, 12000);//24個漢字   評論
        sheet.setColumnWidth(5, 3000);//6個漢字   分類1
        sheet.setColumnWidth(6, 3000);//6個漢字   分類2
        sheet.setColumnWidth(7, 4000);//8個漢字   分類3
        sheet.setColumnWidth(8, 2500);//5個漢字   情感識別
        sheet.setColumnWidth(9, 2000);//4個漢字   建議類
        sheet.setColumnWidth(10, 2500);//5個漢字   訂單號
        sheet.setColumnWidth(11, 2500);//5個漢字   航班號
        sheet.setColumnWidth(12, 3500);//7個漢字   航班日期
        sheet.setColumnWidth(13, 4000);//8個漢字   訂單聯系人
        sheet.setColumnWidth(14, 4000);//8個漢字   聯系電話
        sheet.setColumnWidth(15, 10000);//20個漢字  備注

        double rn = 20.00 , cn = 24.00 ,c1 = 6.00 ,c2 = 6.00 ,c3 = 8.00 , nn = 8.00;//ca應該為20可是導出結果卻換行不對所以改成16保險一點
        //計算  設置自定義行高
        for (int i = 0; i < commentVOs.size(); i++) {
            //計算行高
            CommentVo commentVo = commentVOs.get(i);
            int charNumC1 =  (int)Math.ceil( dealTrim(commentVo.getClassfyFirst()).length() / c1 );//分類1
            int charNumC2 =  (int)Math.ceil( dealTrim(commentVo.getClassfySecond()).length() / c2 );//分類2
            int charNumC3 =  (int)Math.ceil( dealTrim(commentVo.getClassfyThird()).length() / c3 );//分類2
            int charNumRemark =  (int)Math.ceil( dealTrim(commentVo.getRemark()).length() / rn );//備注
            int charNumComent =   (int)Math.ceil( dealTrim(commentVo.getCmtText()).length() / cn);//評論
            int charNumName =   (int)Math.ceil( dealTrim(commentVo.getcName()).length() / nn);//訂單聯系人
            List<Integer> input = new ArrayList<Integer>();
            //input.add(charNum+num);//charNum 是分類的條數,每條一行。num是分類字數大於20的情況就多分配一行
            input.add(charNumC1);
            input.add(charNumC2);
            input.add(charNumC3);
            input.add(charNumRemark);
            input.add(charNumComent);
            input.add(charNumName);
            int rowNum = Collections.max(input);

            Row row = sheet.getRow(i+1);
            row.setHeight((short)(sheet.getDefaultRowHeight()*rowNum));
        }
        request.getSession().setAttribute("currentNum",commentVOs.size());
    }

 

代碼中的request可忽略,那是我做的進度條,


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM