部分图文来源于网路,如有侵权,请联系博主删除,谢谢!
一、前言
关于导出 Excel 文件,可以说是大多数服务中都需要集成的功能。那么,要如何优雅快速地(偷懒地)去实现这个功能呢?
通常情况下我们用 Apache 开源框架 poi, 或者 jxl 都可以实现啊。面向百度编程,把代码模板 copy 下来,根据自己的业务再改改,能有多难?(图片来自于网络) 二、Apache poi、jxl 的缺陷
在说如何实现之前,我们先来讨论一下传统 Excel 框架的不足!除了上面说的,Apache poi、jxl 都存在生成 excel 文件不够简单优雅快速外,它们都还存在一个严重的问题,那就是非常耗内存,严重时会导致内存溢出。
POI 虽然目前来说,是 excel 解析框架中被使用最广泛的,但这个框架并不完美。
为什么这么说呢?
开发者们大部分使用 POI,都是使用其 userModel 模式。而 userModel 的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,但是还是可控的。
然而 userModel 模式最大的问题是在于,对内存消耗非常大,一个几兆的文件解析甚至要用掉上百兆的内存。现实情况是,很多应用现在都在采用这种模式,之所以还正常在跑是因为并发不大,并发上来后,一定会OOM或者频繁的 full gc。
三、阿里出品的 EasyExcel,安利一波
什么是 EasyExcel? 见名知意,就是让你操作 Excel 异常的酸爽。先来看下 EasyExcel GitHub 官方截图:
四、EasyExcel 解决了什么
传统 Excel 框架,如 Apache poi、jxl 都存在内存溢出的问题;
传统 excel 开源框架使用复杂、繁琐;
EasyExcel 底层还是使用了 poi, 但是做了很多优化,如修复了并发情况下的一些 bug, 具体修复细节,可阅读官方文档https://github.com/alibaba/easyexcel;
五、EasyeExcel核心原理
写有大量数据的xlsx文件时,POI为我们提供了SXSSFWorkBook类来处理,这个类的处理机制是当内存中的数据条数达到一个极限数量的时候就flush这部分数据,再依次处理余下的数据,这个在大多数场景能够满足需求。 读有大量数据的文件时,使用WorkBook处理就不行了,因为POI对文件是先将文件中的cell读入内存,生成一个树的结构(针对Excel中的每个sheet,使用TreeMap存储sheet中的行)。如果数据量比较大,则同样会产生java.lang.OutOfMemoryError: Java heap space错误。POI官方推荐使用“XSSF and SAX(event API)”方式来解决。 分析清楚POI后要解决OOM有3个关键。
1、文件解压文件读取通过文件形式
2、避免将全部全部数据一次加载到内存
采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理。
3、抛弃不重要的数据
Excel解析时候会包含样式,字体,宽度等数据,但这些数据是我们不关系的,如果将这部分数据抛弃可以大大降低内存使用。Excel中数据如下Style占了相当大的空间。
六、快速上手
6.1添加依赖
com.alibaba
easyexcel
1.1.2-beta5
6.2七行代码搞定Excel生成(写数据)
上面这段示例代码中,有两个点很重要:
①:WriteModel 这个对象就是要写入 Excel 的数据模型对象,等等,你这好像不行吧?表头 head,以及每个单元格内的数据顺序都没指定,能达到想要的效果么?别急,后面会讨论这块!
②:创建需要写入的数据集,当然了,正常业务中,这块都是从数据库中查询出来的。
PS: 如果说写入的数据量很大,需要做分片查询再写入的处理,否则可能会 OOM(Out of Memory).
回过头来,我们来看看 WriteModel 这个对象内部
ExayExcel 提供注解的方式, 来方便的定义 Excel 需要的数据模型:
①:首先,定义的写入模型必须要继承自 BaseRowModel.java;
②:通过 @ExcelProperty 注解来指定每个字段的列名称,以及下标位置;
同时,上面定义的 createModelList() 方法也很简单,通过循环,创建一个写入模型的 List 集合:跑一跑单元测试看下实际效果:
6.3 从Excel中读数据
详情参考:https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/ReadTest.java
七、特殊场景支持
在实际的业务中,我们还会有一些特需的需求,比如说下面这些。
7.1 动态生成 Excel 内容
上面的例子是基于注解的,也就是说表头 head, 以及内容都是写死的,换句话说,我定义好了一个数据模型,那么,生成的 Excel 文件也就是只能遵循这种模型来了,但是,实际业务中可能会存在动态变化的需求,要怎么做呢?
动态添加表头,也可自由组合复杂表头,代码如下:
创建动态数据,注意这里的数据类型是 Object:
7.2 自定义表头以及内容样式
设置样式的方法createTableStytle()具体内容如下:我们可以通过 TableStyle 这个类来设置表头、表格主题的样式。
看看效果图:
7.3 合并单元格
我们可以通过 merge() 方法来合并单元格:注意下标是从 0 开始的,也就是说合并了第六行到第七行,其中的第一列到第五列.
7.4 自定义处理
对于更复杂的处理,EasyExcel 预留了 WriterHandler 接口来,允许你自定义处理代码:接口中定义了三个方法:
sheet(): 在创建每个 sheet 后自定义业务逻辑处理;
row(): 在创建每个 row 后自定义业务逻辑处理;
cell(): 在创建每个 cell 后自定义业务逻辑处理;
我们实现了该接口后,编写自定义逻辑处理代码,然后调用 getWriterWithTempAndHandler()静态方法获取 ExcelWriter 对象时,传入 WriterHandler 的实现类即可
八、Web 下载示例代码
九、需要注意的点
9.1 写入大数据时,需分片
比如说,我们需要从数据库中查询出数据量较大时,我们需要在业务层做分片处理,也就是,我们需要分多次查询,再写入,防止内存溢出 OOM.
9.2 Excel 最大行数问题
Excel 03, 07 版本均有行数、列数的限制:
csv 由于是文本文件,实际上没有最大行数的限制,但是用 Excel 客户端打开还是多了不显示。
也就是说,如果你想写入更多的行数是不行的,强行这么做,程序会报类似如下异常
Invalid row number (1048576) outside allowable range (0..1048575)
如何解决呢?
1.分多个 Excel 文件写入;
2.同一个 Excel 文件,分多个 Sheet 写入;
十、GitHub 源码地址
https://github.com/weiwosuoai/spring-boot-tutorial/tree/master/spring-boot-excel
REF:https://github.com/alibaba/easyexcel