EasyExcel写入百万级数据到多sheet

EasyExcel是什么?

快速、简单避免OOM的java处理Excel工具

一、项目需求

       从mongo库中查询数据,导出到excel文件中。但是动态导出的excel有多少列、列名是什么、有多少sheet页都需要动态获取。所以生成的excel也必须是动态生成,不能通过注解配置对象映射。而且写入的数据量,有可能达到100W级,使用传统的POI工具,需要把excel数据全部加载到内存空间,内存空间很容易OOM。所以选择了阿里的EasyExcel,据说可以高效的解决POI的OOM问题。

二、测试Demo

   1、引入的pom依赖

1<dependency> 2 <groupId>com.alibaba</groupId> 3 <artifactId>easyexcel</artifactId> 4 <version>1.1.2-beta5</version> 5</dependency> 6<dependency> 7 <groupId>org.apache.poi</groupId> 8 <artifactId>poi-ooxml</artifactId> 9 <version>3.17</version> 10</dependency>

   2、测试代码

1package com.movitech.product.datahub.util; 2 3import com.alibaba.excel.EasyExcelFactory; 4import com.alibaba.excel.ExcelReader; 5import com.alibaba.excel.ExcelWriter; 6import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.context.WriteContext; import com.alibaba.excel.event.AnalysisEventListener; import com.alibaba.excel.event.WriteHandler; import com.alibaba.excel.metadata.Sheet; import com.alibaba.excel.metadata.Table; import com.alibaba.excel.support.ExcelTypeEnum; import com.alibaba.excel.write.ExcelBuilderImpl; import org.apache.poi.ss.usermodel.*; import java.io.*; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; /** * @Author JAY * @Date 2019/8/29 11:00 * @Description TODO **/ public class EasyExcelUtil { public static String excelFilePath = "C:\\Users\\lenovo\\Desktop\\Jay01-(jay01)-v5自定义导入数据.xls"; public static void main(String[] args) { try { writeExcel(excelFilePath); } catch (IOException e) { e.printStackTrace(); } } public static void writeExcel(String excelFile) throws IOException { // 文件输出位置 OutputStream out = new FileOutputStream(excelFile); ExcelWriter writer = EasyExcelFactory.getWriter(out); // 动态添加表头,适用一些表头动态变化的场景 Sheet sheet1 = new Sheet(1, 0); sheet1.setSheetName("第一个sheet"); // 创建一个表格,用于 Sheet 中使用 Table table1 = new Table(1); // 无注解的模式,动态添加表头 table1.setHead(createTestListStringHead()); // 写数据 writer.write1(new ArrayList<>(), sheet1, table1); // 动态添加表头,适用一些表头动态变化的场景 Sheet sheet2 = new Sheet(2, 0); sheet2.setSheetName("第2个sheet"); /* 添加TableStyle属性会使内存OOM,没办法满足分批插入100W条数据 TableStyle tableStyle = new TableStyle(); com.alibaba.excel.metadata.Font font = new com.alibaba.excel.metadata.Font(); font.setBold(true); tableStyle.setTableContentFont(font); sheet2.setTableStyle(tableStyle); */ // 创建一个表格,用于 Sheet 中使用 Table table2 = new Table(2); // 无注解的模式,动态添加表头 table2.setHead(createTestListStringHead()); writer.write1(new ArrayList<>(), sheet2, table2); int x = 0; while (x < 1000000) {     // 模拟分批写入数据到excel,每次写入100条 System.out.println("x = " + x); Table tableX = new Table(1);        // 每次从sheet的第几行开始写入 sheet1.setStartRow(x); writer.write1(createDynamicModelList(x), sheet1, tableX); Table tableX2 = new Table(1); sheet2.setStartRow(x); writer.write1(createDynamicModelList(x), sheet2, tableX2); x = x + 100; } // 将上下文中的最终 outputStream 写入到指定文件中 writer.finish(); // 关闭流 out.close(); } private static List<List<Object>> createDynamicModelList(int x) { List<List<Object>> rows = new ArrayList<>(); for (int i= x; i < 100 + x; i++){ List<Object> row = new ArrayList<>(); row.add("字符串-" + i); row.add(Long.valueOf(187837834L) + i); row.add(Integer.valueOf(2233 + i)); row.add("宁-" + i); row.add("微信公众号: demo"); rows.add(row); } return rows; } private static List<List<String>> createTestListStringHead() { // 模型上没有注解,表头数据动态传入 List<List<String>> head = new ArrayList<List<String>>(); List<String> headCoulumn1 = new ArrayList<String>(); List<String> headCoulumn2 = new ArrayList<String>(); List<String> headCoulumn3 = new ArrayList<String>(); List<String> headCoulumn4 = new ArrayList<String>(); List<String> headCoulumn5 = new ArrayList<String>(); headCoulumn1.add("第1列"); headCoulumn2.add("第2列"); headCoulumn3.add("第3列"); headCoulumn4.add("第4列"); headCoulumn5.add("第5列"); head.add(headCoulumn1); head.add(headCoulumn2); head.add(headCoulumn3); head.add(headCoulumn4); head.add(headCoulumn5); return head; } }

   3、执行结果

总结:

 此测试代码可以直接运行测试查看结果。

我配置的jvm运行参数,

 我只给了10M空间,但是往excel中写入100W数据,程序并没有出现OOM。可以看到,使用EasyExcel,确实解决了OOM问题。

但是实际情况,EasyExcel不足以满足我的业务需求。因为除了百万级的数据导出之外,还需要进行sheet页隐藏、行隐藏、列隐藏等操作。目前EasyExcel的API,还没有那么多的功能变化。不过,easyExcel提供了自定义拦截器的功能,貌似可以给excel做样式处理。大致测试了一下,可以隐藏列和sheet,但是不知道怎么隐藏行。测试代码如下:

 (1)隐藏列,通过自定义拦截器

1public static void writeExcelToSheet(String excelFile, Sheet sheet) throws IOException { 2 // 文件输出位置 3 OutputStream out = new FileOutputStream(excelFile); 4 ExcelWriter writer = EasyExcelFactory.getWriterWithTempAndHandler(null, out, ExcelTypeEnum.XLS, true, new WriteHandler() { 5 @Override 6 public void sheet(int i, org.apache.poi.ss.usermodel.Sheet sheet) { 7 sheet.setColumnHidden(0,true); 8 sheet.setColumnHidden(1,true); 9 } 10 11 @Override 12 public void row(int i, Row row) { 13 System.out.println("row : " + row.getRowNum()); 14 } 15 16 @Override 17 public void cell(int i, Cell cell) { 18 System.out.println("cell : " + i); 19 } 20 }); 21 22 23 Table table1 = new Table(1); 24 table1.setHead(createTestListStringHead());// 写数据 25 writer.write1(createDynamicModelList(0), sheet, table1); 26 27 // 将上下文中的最终 outputStream 写入到指定文件中 28 writer.finish(); 29 // 关闭流 30 out.close(); 31 }

  (2)隐藏sheet页,通过反射获取Workbook,用wb来设置隐藏sheet页

1/** 2 * **获取workbook** 3 * 因为EasyExcel这个库设计的原因 4 * 只能使用反射获取workbook 5 * 6 * @param writer 7 * @return 8 */ 9 private static Workbook getWorkbook(ExcelWriter writer) { 10 Workbook workbook = null; 11 try { 12 Class<?> clazz1 = Class.forName("com.alibaba.excel.ExcelWriter"); 13 Field[] fs = clazz1.getDeclaredFields(); 14 for (Field field : fs) { 15 // 要设置属性可达,不然会抛出IllegalAccessException异常 16 field.setAccessible(true); 17 if ("excelBuilder".equals(field.getName())) { 18 ExcelBuilderImpl excelBuilder = (ExcelBuilderImpl) field.get(writer); 19 Class<?> clazz2 = Class.forName("com.alibaba.excel.write.ExcelBuilderImpl"); 20 Field[] fs2 = clazz2.getDeclaredFields(); 21 for (Field field2 : fs2) { 22 field2.setAccessible(true); 23 if ("context".equals(field2.getName())) { 24 WriteContext context = (WriteContext) field2.get(excelBuilder); 25 workbook = context.getWorkbook(); 26 } 27 } 28 } 29 } 30 } catch (ClassNotFoundException e) { 31 e.printStackTrace(); 32 } catch (IllegalAccessException e) { 33 e.printStackTrace(); 34 } 35 return workbook; 36 } 37 38public static void writeExcel(String excelFile) throws IOException { 39 // 文件输出位置 40 OutputStream out = new FileOutputStream(excelFile); 41 ExcelWriter writer = EasyExcelFactory.getWriter(out); 42 43 // 动态添加表头,适用一些表头动态变化的场景 44 Sheet sheet1 = new Sheet(1, 0); 45 sheet1.setSheetName("第一个sheet"); 46 // 创建一个表格,用于 Sheet 中使用 47 Table table1 = new Table(1); 48 // 无注解的模式,动态添加表头 49 table1.setHead(createTestListStringHead()); 50 // 写数据 51 writer.write1(new ArrayList<>(), sheet1, table1); 52 53 // 动态添加表头,适用一些表头动态变化的场景 54 Sheet sheet2 = new Sheet(2, 0); 55 sheet2.setSheetName("第2个sheet"); 56/* 57 添加TableStyle属性会使内存OOM 58 TableStyle tableStyle = new TableStyle(); 59 com.alibaba.excel.metadata.Font font = new com.alibaba.excel.metadata.Font(); 60 font.setBold(true); 61 tableStyle.setTableContentFont(font); 62 sheet2.setTableStyle(tableStyle); 63*/ 64 65 // 创建一个表格,用于 Sheet 中使用 66 Table table2 = new Table(2); 67 // 无注解的模式,动态添加表头 68 table2.setHead(createTestListStringHead()); 69 writer.write1(new ArrayList<>(), sheet2, table2); 70 71 int x = 0; 72 while (x < 10000) { 73 System.out.println("x = " + x); 74 Table tableX = new Table(1); 75 sheet1.setStartRow(x); 76 writer.write1(createDynamicModelList(x), sheet1, tableX); 77 78 Table tableX2 = new Table(1); 79 sheet2.setStartRow(x); 80 writer.write1(createDynamicModelList(x), sheet2, tableX2); 81 82 x = x + 100; 83 } 84 85 //获取workbook,隐藏第2页sheet 86 Workbook workbook = getWorkbook(writer); 87 workbook.setSheetHidden(1,true); 88 89 // 将上下文中的最终 outputStream 写入到指定文件中 90 writer.finish(); 91 // 关闭流 92 out.close(); 93 }

参考资源 https://segmentfault.com/a/1190000019472781,https://github.com/alibaba/easyexcel

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )