基于Java8实现提高Excel读写效率
作者:shuzihua 发布时间:2023-11-25 10:01:37
在POI的使用过程中,对大多数API User来说经常面临两个问题,这也是GridExcel致力解决的问题。
问题1. 仅使用简单的导入导出功能,但每次业务的数据对象结构不同,需要重新编写处理方法,很麻烦!
解决方法
将Excel处理逻辑抽取出来,封装成工具类。
封装条件
与大多数Java API一样,POI把更多的精力放在高级功能的处理上,比如Formula(公式)、Conditional Formatting(条件格式)、Zoom(缩放)等。对于仅仅做数据导入导出功能的API User,很少使用这些高级特性,这允许API用户对POI的使用进行简单的封装。
封装方式
无论是读是写,我们都需要解决Excel中的Columns(列)与Java数据对象Fields(字段)的映射关系,将这种映射关系作为参数(Map对象HashMap或LinkedHashMap),传递给工具类。
对于Columns不难理解,它可以是有序的数字或字母,也可以是其它字符串用来作为首行,表示该列数据的含义。
对于Fields,它的处理需要兼容复杂情况,如下:
查询字段时出现异常
字段或单元格的值为null
该列的值可能对应关联对象、甚至是关联集合中的某个字段值
字段或单元格的值需要做特殊处理,例如value == true?完成:失败;
反射
首先想到,也是大多数封装者都在使用的方式是就是Reflection API,从上文 函数编程 章节我们了解到,反射重量级,会降低代码的性能,同时对复杂情况的处理支持性不够好。
反射+注解
这种方式可以更好的支持复杂情况,但是反射依然会降低性能,同时注解对数据对象会造成代码侵入,而且对该工具类封装者的其他使用者无疑会增加学习成本。
匿名内部类
这种方式也可以很好的支持复杂情况,但是使用匿名内部类的语法显然患有“垂直问题”(这意味着代码需要太多的线条来表达基本概念),太过冗杂。至于性能,应该也不如直接传递函数来的快吧。
函数接口(Lambda)
这种方式是基于第5条方法调用的字节码指令invokeDynamic实现的,直接传递函数代码块,很好的支持复杂情况,性能较高,代码编写更简单结构更加简洁,而且对数据对象代码零侵入。
当然如果你还没有使用Java1.8或更高版本,那么你可以参考匿名内部类或反射+注解,不过还是推荐反射+注解,Alibaba/easyexcel【https://github.com/alibaba/easyexcel】对你来说会是不错的选择。
问题2. Excel导入或导出数据量比较大,造成内存溢出或频繁的Full GC,该如何解决?
解决方法
读Excel —— eventmodel
写Excel —— streaming.SXSSFWorkbook
原理
POI的使用对我们来说很常见,对下面两个概念应该并不陌生:
HSSFWorkbook(处理97(-2007) 的.xls)
XSSFWorkbook(处理2007 OOXML (.xlsx) )
但是对于eventmodel和streaming.SXSSFWorkbook就很少接触了,它们是POI提供的专门用来解决内存占用问题的low level API(低级API),使用它们可以读写数据量非常大的Excel,同时可以避免内存溢出或频繁的Full GC。【https://poi.apache.org/components/spreadsheet/how-to.html】
eventmodel,用来读Excel,并没有将Excel整个加载到内存中,而是允许用户从InputStream每读取一些信息,就交给回调函数或 * ,至于丢弃,存储还是怎么处理这些内容,都交由用户。
streaming.SXSSFWorkbook,用来写Excel(是对XSSFWorkbook的封装,仅支持.xlsx),通过滑动窗口来实现,只在内存中保留滑动窗口允许存在的行数,超出的行Rows被写出到临时文件,当调用write(OutputStream stream)方法写出内容时,再直接从临时内存写出到目标OutputStream。SXSSFWorkbook的使用会产生一些局限性。
Only a limited number of rows are accessible at a point in time.
Sheet.clone() is not supported.
Formula evaluation is not supported
解决途径
https://github.com/liuhuagui/gridexcel 基于Java函数编程(Lambda),支持流式API,使用环境Java1.8或更高,学习成本:
Lambda
https://github.com/alibaba/easyexcel 基于反射+注解+ * ,使用环境Java1.6或以上,学习成本:模型注解
实际上POI官网已经给了用户使用示例,而上述两个工具都只是做了自己的封装实现,使用者只需要拿来用就好。
快速使用
<dependency>
<groupId>com.github.liuhuagui</groupId>
<artifactId>gridexcel</artifactId>
<version>2.2</version>
</dependency>
GridExcel.java
GridExcel.java提供了多种静态方法,可以直接使用,具体式例可参考测试代码(提供了测试数据和测试文件):
https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/ReadTest.java
https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/WriteTest.java
流式API
/**
* 业务逻辑处理方式三选一:
* 1.启用windowListener,并将业务逻辑放在该函数中。
* 2.不启用windowListener,使用get()方法取回全部数据集合,做后续处理。
* 3.readFunction函数,直接放在函数中处理 或 使用final or effective final的局部变量存放这写数据,做后续处理。
* 注意:使用EventModel时readFunction函数的输入为每行的cell值集合List<String>。
* @throws Exception
*/
@Test
public void readXlsxByEventModel() throws Exception {
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("2007.xlsx");
GridExcel.readByEventModel(resourceAsStream,TradeOrder.class,ExcelType.XLSX)
.window(2,ts -> System.out.println(JSON.toJSONString(ts)))//推荐在这里执行自己的业务逻辑
.process(cs ->{
TradeOrder tradeOrder = new TradeOrder();
tradeOrder.setTradeOrderId(Long.valueOf(cs.get(0)));
Consultant consultant = new Consultant();
consultant.setConsultantName(cs.get(3));
tradeOrder.setConsultant(consultant);
tradeOrder.setPaymentRatio(cs.get(16));
return tradeOrder;
},1);
}
/**
* 使用Streaming UserModel写出数据到Excel
* @throws Exception
*/
@Test
public void writeExcelByStreaming() throws Exception {
GridExcel.writeByStreaming(TradeOrder.class)
.head(writeFunctionMap())//对象字段到Excel列的映射
.createSheet()
.process(MockData.data())//模拟数据。在这里设置业务数据集合。
.write(FileUtils.openOutputStream(new File("/excel/test.xlsx")));
}
ReadExcel
ReadExcelByUserModel
Use user model to read excel file. userModel ——
缺点:内存消耗大,会将excel信息全部加载到内存再进行处理。
优点:现成的API,使用和理解更简单。
使用场景:可以处理数据量较小的Excel。
ReadExcelByEventModel
Use event model to read excel file. eventModel ——
缺点:没有现成的API,使用和理解较为复杂,适合中高级程序员(GridExcel的目标之一就是让EventModel的使用变得简单)
优点:非常小的内存占用,并没有在一开始就将所有内容加载到内存中,而是把主体内容的处理(存储,使用,丢弃)都交给了用户,用户可以自定义监听函数来处理这些内容。
使用场景:可以处理较大数据量的Excel,避免OOM和频繁FullGC
WriteExcel
WriteExcelByUserModel
Use user model to write excel file. userModel ——
缺点:会将产生的spreadsheets对象整个保存在内存中,所以write Excel的大小受到堆内存(Heap space)大小限制。
优点:使用和理解更简单。
使用场景:可以写出数据量较小的Excel。
WriteExcelByStreaming
Use API-compatible streaming extension of XSSF to write very large excel file. streaming userModel——
缺点:
仅支持XSSF;
Sheet.clone() is not supported;
Formula evaluation is not supported;
Only a limited number of rows are accessible at a point in time.
优点:通过滑动窗口来实现,内存中只保留指定size of rows的内容,超出部分被写出到临时文件,write Excel的大小不再受到堆内存(Heap space)大小限制。
使用场景:可以写出非常大的Excel。
Issues
在使用工具过程中出现问题,有功能添加或改动需求的可以向作者提Issue:https://github.com/liuhuagui/gridexcel/issues
来源:https://www.cnblogs.com/xinxihua/p/12679252.html


猜你喜欢
- 一、项目简述本系统功能包括:通知公告,老人管理,护工管理,问答管理等等功能。二、项目运行环境配置: Jdk1.8 + Tomcat8.5 +
- 本文介绍了如何使用Spring Security OAuth2构建一个授权服务器来验证用户身份以提供access_token,并使用这个ac
- 1.概述前几天群里有人问如何制作备忘录,感觉这样一个小实例挺适合新手们入门学习使用,所以就抽空做了出来。界面如下图这个备忘录主要包括了如下功
- 前言在【Android】线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤。那么接下来,我
- 前言上一篇小结了一下关于redis的异常测试,今天再来盘一盘 MQ 相关的。MQ 跟 redis 一样,也是现在系统服务中不可或缺的重要中间
- 本文实例为大家分享了Android使用GridView实现横向滚动效果的具体代码,供大家参考,具体内容如下第一次做横向滑动,看了一些列子,基
- 你知道String、StringBuilder、Stringbuffer的区别吗?当你创建字符串的时候,有考虑过该使用哪个吗?别急,这篇文章
- Condition是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean操作。思考:SpringBoo
- 笔者在网上发现了,很多种方法 ,当然咱们找最好用的,也简单的 下面废话不多说直接上代码 我在啰嗦几句 第一这个脚本挂在需要相应的游戏体上 第
- 1.通过用FTP进行上传文件,首先要实现建立FTP连接,一般建立FTP连接,需要知道FTP配置有关的信息。一般要在Bean中建立一个Serv
- 简单概述在日常生活中,我们经常会突发一些奇思妙想,或是一个画面,或是几个符号。这时候无法使用拍照或者打字功能实现,想拿笔记下又身边找不到笔。
- 这一篇网络爬虫的实现就要联系上大数据了。在前两篇java实现网络爬虫和heritrix实现网络爬虫的基础上,这一次是要完整的做一次数据的收集
- 这周末体验了一下挺火的Docker技术,记录学习笔记。>Docker是干什么的Docker 是一个基于Linux容器(LXC-linu
- 本项目主要实现对汽车维修厂的信息化管理功能,主要包含三个角色:管理员,维修师傅,客户。实现的主要功能包含用户管理、配置管理、汽车管理、故障管
- javabean与map的转换有很多种方式,比如:1、通过ObjectMapper先将bean转换为json,再将json转换为map,但是
- 这篇文章主要介绍了Java String的intern用法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- 我们已经写了一些Java程序。之前的每个Java程序都被保存为一个文件,比如Test.java。随后,该程序被编译为Test.class。我
- public class MenuEx extends Activity { private static final String TAG
- 本文实例为大家分享了java实现飞机游戏的具体代码,供大家参考,具体内容如下MyGameFrame类:主要的调用类package sc.wh
- 以最终客户的角度来看,JAR文件就是一种封装,他们不需要知道jar文件中有多少个.class文件,每个文件中的功能与作用,同样可以得到他们希