你一定没用过最简单的使用SXSSFWorkbook快速导出百万条数据
常见的导出可能上百万甚至千万的数据量业务场景
- 历史订单的导出
- 历史订单明细的导出
- 历史支付明细的导出
- 用户信息的导出
- 等等等
遇到这些问题我想你一定头疼过,客服或财务可能会在你睡觉的时候找上你
财务: “大王赶快起来看下系统,系统又卡住了”
大王:“你小子又干啥了”
财务: “我刚导出了一个月的订单明细”
大王:“那应该,没啥问题呀,我们一个月撑死也就1w笔订单”
财务: “上个月不是搞了个活动吗,现在有100w笔订单,然后就导不出来了”
大王默默心里吐槽,但还是从床上爬起来,打开日志一看,果然是这个问题:“内存溢出”
- 因为往常订单少,根据时间查询订单没有分页,大量数据查询超时
- 之前使用XSSFWorkbook下载的时候大量数据转换的输出流内存溢出
解决方案
- 对大量数据进行分页查询
- 导出方法使用SXSSFWorkbook解决
分页就不说了,自己实现
那如何最简单的使用SXSSFWorkbook快速导出百万条数据呢?
-
首先使用造好的*:hutool的ExcelUti
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency>
PS. Hutool这个工具很好,错过一定会后悔
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
Hutool中的工具方法来自于每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;
Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。
-
测试代码
public static void main(String[] args) { //耗时计算 TimeInterval timer = DateUtil.timer(); List<List<Integer>> rows = CollUtil.newArrayList(); for (int i = 0; i < 1000000; i++) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); rows.add(list); } BigExcelWriter writer = ExcelUtil.getBigWriter("e:/test3.xlsx"); // 一次性写出内容,使用默认样式 writer.write(rows); // 关闭writer, writer.close(); System.out.println("执行时间:"+timer.intervalSecond()); }
执行导出100w条数据耗时17s ,注意数据量最大行数:1048576
-
我的结合Hutool在业务中使用
/** * 导出excel * * @param response * @param name 文件名 * @param rows 业务数据 ArrayList<Map<String, Object>> */ public static void writeExcel(HttpServletResponse response, String name, List<Map<String, Object>> rows) { // 通过工具类创建writer ExcelWriter writer = cn.hutool.poi.excel.ExcelUtil.getBigWriter(); // 一次性写出内容,使用默认样式 writer.write(rows, true); write(response, name, writer); } /** * 下载 * * @param response * @param name * @param writer */ public static void write(HttpServletResponse response, String name, ExcelWriter writer) { // response为HttpServletResponse对象 response.setContentType("application/vnd.ms-excel;charset=utf-8"); String fileName = name + ".xls"; ServletOutputStream out = null; try { // test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码 response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1")); out = response.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } writer.flush(out); // 关闭writer,释放内存 writer.close(); }
//查出商品数据 Pager<RespCommoditySalesVo> commoditySalesVos = commoditySalesService.commoditySalesRealTime(suParamsVo); List<Map<String, Object>> rows = CollUtil.newArrayList(); commoditySalesVos.getContent().forEach(commoditySales -> { Map<String, Object> row = new LinkedHashMap<>(); //标题-数据 row.put("商品名称", commoditySales.getName()); row.put("规格", commoditySales.getWeight()); row.put("实时销量", commoditySales.getSales()); row.put("剩余可售库存", commoditySales.getInventory()); row.put("物料号", commoditySales.getSku()); row.put("成本", commoditySales.getCostPrice()); row.put("上下架", CommodityShelvesEnum.getValue(commoditySales.getShelves())); rows.add(row); }); ExcelUtil.writeExcel(response, "商品实时销量", rows);
查询出业务数据组装在map里,key为标题,value为对应数据,然后调用ExcelUtil的write方法写出到excel;
-
具体业务参数格式可以参照 https://hutool.cn/docs/#/poi/Excel%E7%94%9F%E6%88%90-ExcelWriter
为什么使用SXSSFWorkbook这么快:
这是它的官方文档,我就不赘述了:https://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api
懒得点链接看的话,这是谷歌翻译:
SXSSF(软件包:org.apache.poi.xssf.streaming)是XSSF的API兼容流扩展,可用于必须生成非常大的电子表格且堆空间有限的情况。SXSSF通过限制对滑动窗口内的行的访问来实现其低内存占用,而XSSF允许对文档中的所有行进行访问。不再在窗口中的较旧的行将被写入磁盘,因此无法访问。
您可以在工作簿构建时通过新的SXSSFWorkbook(int windowSize)指定窗口大小, 也可以通过SXSSFSheet#setRandomAccessWindowSize(int windowSize)在每张纸上设置窗口大小。
当通过createRow()创建新行并且未刷新记录的总数超过指定的窗口大小时,索引值最低的行将被刷新,并且无法再通过getRow()进行访问。
默认窗口大小为100,由SXSSFWorkbook.DEFAULT_WINDOW_SIZE定义。
windowSize -1表示访问不受限制。在这种情况下,所有未通过调用flushRows()进行刷新的记录都可以用于随机访问。
请注意,SXSSF分配临时文件,您必须始终通过调用dispose方法来明确清理这些文件。
SXSSFWorkbook默认使用内联字符串而不是共享字符串表。这是非常有效的,因为不需要将文档内容保留在内存中,但是众所周知,生成的文档与某些客户端不兼容。启用共享字符串后,文档中的所有唯一字符串都必须保留在内存中。与禁用共享字符串相比,根据您的文档内容,这可能会使用更多的资源。
请注意,根据您正在使用的功能,有些东西仍然可能会占用大量内存,例如,合并区域,超链接,注释等仍然仅存储在内存中,因此,如果广泛使用。
在决定是否启用共享字符串之前,请仔细检查您的内存预算和兼容性需求。
上一篇: 张成的日常问题整理(新)