zoukankan      html  css  js  c++  java
  • 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盘下的 AppDataLocalTemppoifiles    例如我的:C:Users11336AppDataLocalTemppoifiles

    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可忽略,那是我做的进度条,
  • 相关阅读:
    转载——关于scanf造成死循环
    转载——关于C#延时
    2013.02.13——笔记
    最近计划
    关于毕业设计——2013.4.12
    关于c#中combobox赋值问题
    使用DWE编辑对话框窗体
    Insert New Class (a2BusNew under BusItem)
    将TCE链接加入新工作通知(NewWorkAssignment,Sig)邮件中
    创建Relation并Add到数据库
  • 原文地址:https://www.cnblogs.com/UncleWang001/p/10106859.html
Copyright © 2011-2022 走看看