🏆【Alibaba 工具型技术系列】「EasyExcel 技术专题」摒除 OOM!让你的 Excel 操作变得更加优雅和安全
前提概要
针对于后端开发者而言的,作为报表的导入和导出是一个很基础且有很棘手的问题!之前常用的工具和方案大概有这么几种:
JXL(Java Excel API 工具服务),此种只支持 xls 的文件格式,而且对于内存的管理特别的差,现在基本不用了!
目前大多数会操作 Excel 工具服务或者解析都是利用 Apache POI 进行操作。
其他第三方的工具很多也是基于 POI 作为实现基础!
存在隐患问题
因为当数据量特别大的时候,比如:说 Excel 导出,如果数据量在百万级,很有可能会出现俩点内存溢出的问题以及页面极具卡顿。
解决方案
首先,在百万级大数据量 Excel 文件的导出我们可以采用分批查询数据来避免内存溢出的剧增!
此外,POI 给出的方案是:使用 SXSSFWorkbook 方式缓存数据到文件上以解决下载大文件 EXCEL 卡死页面的问题。
SXSSFWorkbook 数据模型:主要可以解决在下载传输到浏览器的时候大 Excel 文件转换的输出流内存溢出
此外其还可以通过其构造函数执指定在内存中缓存的行数,剩余的会自动缓存在硬盘的临时目录上,同时,并不会存在页面卡顿的情况。
SXSSF 机制而言还是需要手动进行封装及定制化开发增加了一定的工作量!
POI 的操作方式仍然还是存在内存占用过大的问题,仍会存在内存溢出的隐患。
存在空循环和整除的时候数据有缺陷的问题。
更优秀的选择
对此阿里巴巴发明咯一个“万金油”!EasyExcel(简单 Excel 操作工具,让 Excel 变得更简单!),除此之外它可以将解析的 Excel 的内存占用控制在 KB 级别,并且绝对不会内存溢出,还有就是速度极快, 不用想了就它了!
EasyExcel 的介绍说明
EasyExcel 是一个基于 Java 实现的、以节省内存为主要目标的的读写 Excel 文件的开源项目。经过官方统计,在尽可能节约内存的情况下支持读写百 M 的 Excel 文件的读写操作能力!
源码库(github 地址):https://github.com/alibaba/easyexcel
官方文档:https://alibaba-easyexcel.github.io/index.html
技术原理对比
POI
当利用 POI 去读取 Excel 时,首先会将数据全部加载到内存中,然后返回给调用者,当数据量比较大时,及其容易发生 OOM。以下是执行流程图:
EasyExcel
与 POI 不用的是,EasyExcel 主要是采用 sax 模式一行一行解析,并将一行的解析结果以观察者的模式通知处理,即使数据量较大时也不会发生 OOM,以下是其执行流程图
技术原理图
借用官方图:
节省内存的开销
借用官方图片:https://alibaba-easyexcel.github.io/images/large.png
64M 内存 1 分钟内读取 75M(46W 行 25 列)的 ExcelExcel 内存开销图。当然还有急速模式能更快,但是内存占用会在 100M 多一点。其实就是拿空间换时间!
Maven 仓库依赖
我用的版本是 2.2.6
注: 系统内部如果有 POI 的包一定让 poi 和 poi-ooxml 的版本要保持一致。
注: 如果是 springboot2.0,则不需要 poi 依赖,如果是 1.0,则需要 poi 依赖,并且 poi 和 poi-ooxml 的版本要保持一致。
基础 API 介绍(参考官方文档)
EasyExcel 入口类,用于构建开始各种操作,属于典型的门面+工厂模式
invoke 方法代表每解析一行就会调用一次,data 数据表示解析出来一行的数据。
doAfterAllAnalysed 该方法表示将所有数据解析完毕以后才会去调用该方法。
内部实现机制:(可以理解成一个 excel 对象,一个 excel 只要构建一个)
ExcelReaderBuilder 构建出一个 ReadWorkbook
excelType 当前 excel 的类型 默认会自动判断
inputStream 与 file 二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用 file 参数。因为使用了 inputStream easyexcel 会帮忙创建临时文件,最终还是 file
file 与 inputStream 二选一。读取文件的文件。
autoCloseStream 自动关闭流。
readCache 默认小于 5M 用 内存,超过 5M 会使用 EhCache,这里不建议使用这个参数。
useDefaultListener @since 2.1.4 默认会加入 ModelBuildEventListener 来帮忙转换成传入 class 的对象,设置成 false 后将不会协助转换对象,自定义的监听器会接收到 Map<Integer,CellData>对象,如果还想继续接听到 class 对象,请调用 readListener 方法,加入自定义的 beforeListener、 ModelBuildEventListener、 自定义的 afterListener 即可。
方法体现:
EasyExcel.read 该方法是用来创建 ExcelReaderBuilder 对象,该对象就是用来解析 Excel 文档
read 方法需要传入三个参数:
第一个参数:需要解析文件的路径,当然除了传入一个文件路径以外,还可以传入 InputStream
第二参数:数据类型的 Class 类型对象,可以不传
第三个参数:事件监听器,在之前介绍这款框架时说过,该框架是基于 SAX 的一种解析,加载一行数据到内存就会去解析一行,主要是为了节约内存。
ExcelWriterBuilder 构建出一个 WriteWorkbook
excelType 当前 excel 的类型 默认 xlsx
outputStream 与 file 二选一。写入文件的流
file 与 outputStream 二选一。写入的文件
templateInputStream 模板的文件流
templateFile 模板文件
autoCloseStream 自动关闭流。
password 写的时候是否需要使用密码
useDefaultStyle 写的时候是否是使用默认头
WriteTable(就把 excel 的一个 Sheet,一块区域看一个 table)参数
tableNo 需要写入的编码。默认 0
内部实现机制:(可以理解成 excel 里面的一页,每一页都要构建一个)
ExcelReaderSheetBuilder:构建出一个 ReadSheet 对象。
sheetNo 需要读取 Sheet 的编码,建议使用这个来指定读取哪个 Sheet。
sheetName 根据名字去匹配 Sheet,excel 2003 不支持根据名字去匹配。
ExcelWriterSheetBuilder 构建出一个 WriteSheet 对象。
需要写入的编码。默认 0
sheetName 需要些的 Sheet 名称,默认同 sheetNo
内部实现机制:(可以理解成 excel 里面的一页,每一页都要构建一个)
ReadListener:每一行读取完毕后都会调用 ReadListener 来处理数据
WriteHandler :每一个操作包括创建单元格、创建表格等都会调用 WriteHandler 来处理数据
所有配置都是继承的,Workbook 的配置会被 Sheet 继承,所以在用 EasyExcel 设置参数的时候,在 EasyExcel...sheet()方法之前作用域是整个 sheet,之后针对单个 sheet。
相关使用注解
ExcelProperty index 指定写到第几列,默认根据成员变量排序。value 指定写入的名称,默认成员变量的名字,多个 value 可以参照快速开始中的复杂头。
ExcelIgnore 默认所有字段都会写入 excel,这个注解会忽略这个字段
DateTimeFormat 日期转换,将 Date 写到 excel 会调用这个注解。里面的 value 参照 java.text.SimpleDateFormat
NumberFormat 数字转换,用 Number 写 excel 会调用这个注解。里面的 value 参照 java.text.DecimalFormat
ExcelIgnoreUnannotated 默认不加 ExcelProperty 的注解的都会参与读写,加了不会参与。
使用案例:
注意 :如果使用该类的对象去装载 Excel 中的数据,那么读取时就只能读取以下样式的 Excel 数据模型,否则数据部分丢失或者全部丢失
实战案例
读取 Excel 实现 Demo
数据模型 DemoModel
读取回调监听器
注意:DemoModelAnalysisListener 不要是单例模式或者全局共享,要每次读取 excel 都要 new,否则会出现复用之前读取的过程数据!
读取操作测试类
写入 Excel 实现 Demo
写入数据模型
写入操作代码
复杂头写入
复杂头的写入操作
版权声明: 本文为 InfoQ 作者【李浩宇/Alex】的原创文章。
原文链接:【http://xie.infoq.cn/article/485eaa2369075f6beee3528dd】。文章转载请联系作者。
评论