赞
踩
Spring Boot系列: 点击查看Spring Boot系列文章
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称,能避免OOM(使用poi可能会OOM)。
EasyExcel能大大减少占用内存的主要原因是,在解析Excel时没有将文件一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
以下是官方的介绍:
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模式。在上层做了模型转换的封装,让使用者更加简单方便。
性能:64M内存1分钟内读取75M(46W行25列)的Excel,当然还有急速模式能更快,但是内存占用会在100M多一点
1、添加依赖,EasyExcel的更新是特快的,对于EasyExcel,我的建议是使用较新的版本,因为比较旧的版本会有些小bug,所以我用的是最新的2.2.6
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
下面我们来按照官网的例子来实践下easyexcel的使用,以下只演示最基础的读写,需要更复杂的功能请查看官方文档
官方文档:https://www.yuque.com/easyexcel/doc/easyexcel
1、创建excel对应的实体对象
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
解析: @ExcelProperty表示excel的列名,写入excle时会自动帮我们创建列名,并将相应的数据赋值到对应的列中
2、获取写入数据,在这里是自己生成的数据对象,实际中,我们根据自己业务来生成数据对象
//创建写入数据
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
3、测试写入excel
@Test public void simpleWrite() { // 写法1 String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx"; System.out.println(TestFileUtil.getPath()); // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // 写法2 fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写 ExcelWriter excelWriter = null; try { excelWriter = EasyExcel.write(fileName, DemoData.class).build(); WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); excelWriter.write(data(), writeSheet); } finally { // 千万别忘记finish 会帮忙关闭流 if (excelWriter != null) { excelWriter.finish(); } } }
解析:
第一种写入的方法是通过pathName(要写入的路径文件名,如果没有路径则默认为项目根路径下)和excel实体类的class类型来生成一个ExcelWriterBuilder构造器。通过ExcelWriterBuilder的sheet(String sheetName)来生成一个指定名称的sheet。然后通过doWrite来进行写入,doWrite会自动关闭流
sheet(String sheetName)默认是生成第一个sheet
第二种方法写入方法是先通过pathName(要写入的路径文件名,如果没有路径则默认为项目根路径下)和excel实体类的class类型来生成一个ExcelWriter,然后创建WriteSheet对象(sheet名称和数量),最后调用ExcelWriter的write来写入数据,不自动关闭流,所以需要手动关闭流。
第二种方法可以写入多个sheet,如下代码,就写入了两个sheet
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet(0,"模板1").build();
WriteSheet writeSheet1 = EasyExcel.writerSheet(1,"模板2").build();
excelWriter.write(data(), writeSheet);
excelWriter.write(data(),writeSheet1);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
解析:写入多个sheet只需要创建多个WriteSheet 对象,然后调用多次ExcelWriter 的write即可。
运行测试方法后,可以在项目的根路径下看到生成的excel文件
1、创建回调监听器,监听器用于处理EasyExcel中读取的数据
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 public class DemoDataListener extends AnalysisEventListener<DemoData> { private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class); /** * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; List<DemoData> list = new ArrayList<DemoData>(); /** * 每解析一条数据都会来调用这个方法 * * @param data * one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(DemoData data, AnalysisContext context) { LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data)); list.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list list.clear(); } } /** * 每个sheet的所有数据解析完成了 都会来调用这个方法 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); LOGGER.info("所有数据解析完成!"); } /** * 将读取到的数据存储到数据库 */ private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); //存储到数据库 LOGGER.info("存储数据库成功!"); } }
解析:EasyExcel的回调监听器其实就是一个继承AnalysisEventListener抽象类的类。继承AnalysisEventListener抽象类需要实现两个方法,一个invoke()是每解析一条数据都要调用一次的方法,我们在这个方法里可以对读取到的数据进行处理。还有一个方法是doAfterAllAnalysed(),这个方法是读取完一个sheet所有数据后调用的一个方法,我们根据自己的需求来进行逻辑处理,如:确保最后遗留的数据也存储到数据库。
2、读取excel
@Test public void simpleRead() { // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 // 写法1: String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); // 写法2: fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build(); ReadSheet readSheet = EasyExcel.readSheet(0).build(); ExcelReader read = excelReader.read(readSheet); } finally { if (excelReader != null) { // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish(); } } }
注:file.separator这个代表系统目录中的间隔符,说白了就是斜线,不过有时候需要双线,有时候是单线,你用这个静态变量就解决兼容问题了。在 UNIX 系统上,此字段的值为 ‘/’;在 Microsoft Windows 系统上,它为 ‘’。
上述例子都是读取一个sheet,有时候我们需要读取全部sheet或者多个sheet,那么我们看下面的例子
// 读取全部sheet // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll(); //读取全部sheet,使用doReadAll方法。doReadAll方法会自动关闭流,所以我们不需要手动关流 public void doReadAll() { ExcelReader excelReader = build(); excelReader.readAll(); excelReader.finish(); } // 读取部分sheet ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName).build(); // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener ReadSheet readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); ReadSheet readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build(); // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能 excelReader.read(readSheet1, readSheet2); } finally { if (excelReader != null) { // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的 excelReader.finish(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。