解决java poi海量数据导出内存溢出问题

找了很多天的解决方法,一直被分页查询绕进去了,其实数据都能查出来的,真正卡的地方是ExcelExportUtil类下调错了方法。



最开始调用的方法是(标记的地方):  workbook = new HSSFWorkbook();和 workbook = new XSSFWorkbook();

这两个方法就是导出Excel的最关键的方法,接下来我来说说这两个方法作用:

1.HSSFWorkbook:是操作Excel2003以前(包括2003)的版本,扩展名是.xls;

2.XSSFWorkbook:是操作Excel2007的版本,扩展名是.xlsx;

对于不同版本的EXCEL文档要使用不同的工具类,如果使用错了,会提示如下错误信息。

org.apache.poi.openxml4j.exceptions.InvalidOperationException

org.apache.poi.poifs.filesystem.OfficeXmlFileException

当数据量超出65536条后,在使用HSSFWorkbook或XSSFWorkbook,程序会报OutOfMemoryError:Javaheap space;内存溢出错误。

而我们的数据量达到了9W条数据,用这两个方法肯定是报内存溢出的错误的。

最终我找到的解决方法是:从POI 3.8版本开始,提供了一种基于XSSF的低内存占用的API----SXSSFWorkbook。

3.SXSSFWorkbook-来至官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。您可以提供用作书面数据基础的模板工作簿。有关详细信息,请参见https://poi.apache.org/spreadsheet/how-to.html#sxssf。请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释......仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。SXSSFWorkbook默认使用内联字符串而不是共享字符串表。这非常有效,因为没有文档内容需要保存在内存中,但也被称为制作与某些客户不兼容的文档。在启用共享字符串的情况下,文档中的所有唯一字符串必须保存在内存中。根据您的文档内容,这可能比共享字符串被禁用时使用更多的资源。在决定是否启用共享字符串之前,请仔细检查您的内存预算和兼容性需求。

而在poi架包中提供的方法用的是:


当数据量超过1000条时就会调用SXSSFWorkbook方法,从而解决了海量的数据导出会发生内存溢出的问题。

 public void exportContacts(final PortalUserPage page, final HttpServletRequest request,
            final HttpServletResponse response) throws Exception {

        final String name = "通讯录列表";

        // 去掉分页
        page.setPage(1);
        page.setRows(Integer.MAX_VALUE);
        final List<CmssPortalUser> dataList = (List<CmssPortalUser>) this.queryPortalUserListWithGroup(page);
        final List<PortalUserExcelDto> userList = new ArrayList<>();
        for (final CmssPortalUser user : dataList) {
            final PortalUserExcelDto userDto = new PortalUserExcelDto();
            userDto.setIndex(user.getIndex() != null ? user.getIndex().toString() : "0");
            userDto.setRealName(user.getRealName());
            userDto.setCompany(user.getCompany());
            userDto.setCompanyBranch(user.getCompanyBranch());
            userDto.setDepartment(user.getDepartment());
            userDto.setTitle(user.getTitle());
            userDto.setPhone(user.getPhone());
            userDto.setLoginName(user.getLoginName());
            userDto.setProducts(user.getProducts());
            userDto.setEmail(user.getEmail());
            userDto.setGroup(user.getGroup());
            if (user.getOpenTime() != null) {
                // 开通时间日期格式化
                final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                userDto.setOpenTime(format.format(user.getOpenTime()));
            }
            // 判断全局预览
            // 根据创建者ID查询该用户是否可以浏览全局
            user.setIfGlobelView(shareJedis.ifGlobelView(user.getId().longValue()) ? 1 : 0);
            if (user.getIfGlobelView() != null) {
                if (user.getIfGlobelView() == Constants.CURRENCY.ZERO) {
                    userDto.setIfGlobelView("否");
                }
                if (user.getIfGlobelView() == Constants.CURRENCY.ONE) {
                    userDto.setIfGlobelView("是");
                }
            }
            // 判断状态
            if (StringUtils.isNotBlank(user.getStatus())) {
                if (Constants.CONTACTS_STATUS.UNAVAILABILITY.equals(user.getStatus())) {
                    userDto.setStatus("停用");
                }
                if (Constants.CONTACTS_STATUS.AVAILABILITY.equals(user.getStatus())) {
                    userDto.setStatus("启用");
                }
            }
            // VIP类别
            userDto.setVipCategory(user.getVipCategoryName() != null ? user.getVipCategoryName() : null);
            // Vip级别
            userDto.setVipLevel(user.getVipLevelName() != null ? user.getVipLevelName() : null);
            // 判断短信通知
            if (user.getIsSendSms() != null) {
                if (Constants.UNIVERSAL_CONSTANT.ZERO.equals(user.getIsSendSms())) {
                    userDto.setIsSendSms("否");
                }
                if (Constants.UNIVERSAL_CONSTANT.ONE.equals(user.getIsSendSms())) {
                    userDto.setIsSendSms("是");
                }
            }
            userList.add(userDto);
        }

        final ExportParams params = new ExportParams(name, name, ExcelType.XSSF);
        final Workbook workbook = ExcelExportUtil.exportExcel(params, PortalUserExcelDto.class, userList);

        try {
            final String agent = request.getHeader("User-Agent").toLowerCase();
            // 开始下载文件
            final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
            String filename = format.format(new Date().getTime()) + "-" + name + ".xlsx";
            if (!StringUtils.isEmpty(agent) && agent.contains("msie") || agent.contains("like gecko")
                    || (agent.indexOf("rv") > 0 && agent.indexOf("firefox") == -1)) {
                filename = URLEncoder.encode(filename, "UTF-8");
            } else {
                filename = new String(filename.getBytes("UTF-8"), "ISO8859-1");
            }
            response.setContentType("application/octet-stream");

            response.setHeader("Content-Disposition", "attachment;filename=".concat(filename));

            final OutputStream out = response.getOutputStream();
            workbook.write(out);
            out.flush();
            out.close();
        } catch (final Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

这部分代码是我所写的批量导出海量数据的部分代码,希望可以帮助和我遇到一样错误的人。


  • 18
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值