EasyExcel 简介

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

文档地址 https://github.com/alibaba/easyexcel

更多的使用方法可以查看官方文档 https://github.com/alibaba/easyexcel

依赖

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>easyexcel</artifactId>
  4. <version>2.2.6</version>
  5. </dependency>

写入

这里使用简单的写入

  1. /**
  2. * 最简单的写
  3. * <p>
  4. * 1. 创建excel对应的实体对象 参照{@link DemoData}
  5. * <p>
  6. * 2. 直接写即可
  7. */
  8. @Test
  9. public void simpleWrite() {
  10. // 写法1
  11. String fileName = "E:/" + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
  12. // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
  13. // 如果这里想使用03 则 传入excelType参数即可
  14. EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
  15. }
  16. /**
  17. * 要写入的数据
  18. */
  19. private List<DemoData> data() {
  20. List<DemoData> list = new ArrayList<DemoData>();
  21. for (int i = 0; i < 10; i++) {
  22. DemoData data = new DemoData();
  23. data.setString("字符串" + i);
  24. data.setDate(new Date());
  25. data.setDoubleData(0.56);
  26. list.add(data);
  27. }
  28. return list;
  29. }

基础数据类

DemoData

  1. /**
  2. * 基础数据类
  3. *
  4. * @author Jiaju Zhuang
  5. **/
  6. @Data
  7. public class DemoData {
  8. @ExcelProperty("字符串标题")
  9. private String string;
  10. @ExcelProperty("日期标题")
  11. private Date date;
  12. @ExcelProperty("数字标题")
  13. private Double doubleData;
  14. /**
  15. * 忽略这个字段
  16. */
  17. @ExcelIgnore
  18. private String ignore;
  19. }

读取

最简单的读取

  1. /**
  2. * 最简单的读
  3. * <p>
  4. * 1. 创建excel对应的实体对象 参照{@link DemoData}
  5. * <p>
  6. * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
  7. * <p>
  8. * 3. 直接读即可
  9. */
  10. @Test
  11. public void simpleRead() {
  12. // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  13. // 写法1:
  14. String fileName = "E:/simpleWrite1602827890506.xlsx";
  15. // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
  16. EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
  17. }

基础数据类

DemoData

  1. /**
  2. * 基础数据类.这里的排序和excel里面的排序一致
  3. *
  4. * @author Jiaju Zhuang
  5. **/
  6. @Data
  7. public class DemoData {
  8. private String string;
  9. private Date date;
  10. private Double doubleData;
  11. }

监听类

  1. /**
  2. * 模板的读取类
  3. *
  4. * @author Jiaju Zhuang
  5. */
  6. // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
  7. public class DemoDataListener extends AnalysisEventListener<DemoData> {
  8. private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
  9. /**
  10. * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
  11. */
  12. private static final int BATCH_COUNT = 5;
  13. List<DemoData> list = new ArrayList<DemoData>();
  14. /**
  15. * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
  16. */
  17. private DemoDAO demoDAO;
  18. public DemoDataListener() {
  19. // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
  20. demoDAO = new DemoDAO();
  21. }
  22. /**
  23. * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
  24. *
  25. * @param demoDAO
  26. */
  27. public DemoDataListener(DemoDAO demoDAO) {
  28. this.demoDAO = demoDAO;
  29. }
  30. /**
  31. * 这个每一条数据解析都会来调用
  32. *
  33. * @param data
  34. * one row value. Is is same as {@link AnalysisContext#readRowHolder()}
  35. * @param context
  36. */
  37. @Override
  38. public void invoke(DemoData data, AnalysisContext context) {
  39. LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
  40. list.add(data);
  41. // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
  42. if (list.size() >= BATCH_COUNT) {
  43. saveData();
  44. // 存储完成清理 list
  45. list.clear();
  46. }
  47. }
  48. /**
  49. * 所有数据解析完成了 都会来调用
  50. *
  51. * @param context
  52. */
  53. @Override
  54. public void doAfterAllAnalysed(AnalysisContext context) {
  55. // 这里也要保存数据,确保最后遗留的数据也存储到数据库
  56. saveData();
  57. LOGGER.info("所有数据解析完成!");
  58. }
  59. /**
  60. * 加上存储数据库
  61. */
  62. private void saveData() {
  63. LOGGER.info("{}条数据,开始存储数据库!", list.size());
  64. demoDAO.save(list);
  65. LOGGER.info("存储数据库成功!");
  66. }
  67. }