在业务开发中我们经常会遇到Excel的导入导出,而 Apache POI 是Java开发者常用的API。 【 poi.apache.org/components/… 】
Universal solution for reading and writing simply Excel based on functional programming and POI EventModel
GridExcel是基于Java8函数式编程和POI EventModel实现的用于Excel简单读写的通用解决方案。
什么是 EventModel ?在 POI FAQ (常见问题解答)【 poi.apache.org/help/faq.ht… 】官方给出解释:
The SS eventmodel package is an API for reading Excel files without loading the whole spreadsheet into memory. It does require more knowledge on the part of the user, but reduces memory consumption by more than tenfold. It is based on the AWT event model in combination with SAX. If you need read-only access, this is the best way to do it.
SS eventmodel包是一个用于读取Excel文件而不将整个电子表格加载到内存中的API。 它确实需要用户掌握更多知识,但是将内存消耗减少了十倍以上。 它基于AWT(Abstract Window Toolkit)event model与SAX的结合。 如果您需要只读访问权限,这是最好的方式。
说到函数编程,就不得不提 Lambda表达式 ,如果对Java8中的Lambda不了解或理解不深刻,可以看下甲骨文官网给出的这篇文章,【 www.oracle.com/technetwork… 】,个人认为这是Java8 Lambda从入门到进阶最好的文章之一。
其中函数编程的目的就是实现 代码块传递
,即,将方法作为参数在方法间传递。为此,随着Java语言的发展,不断出现一些解决方案:
内部类
和 匿名内部类
来实现,但是大多数情况下,它们只是被用作事件处理。 码块作为对象
(实际上是数据)不仅有用而且是必要的,但是Java中函数编程还是很笨拙,它需要成长。 注意: 5、6、7 参考《深入理解Java虚拟机》第2版,8.3.3 动态类型语言支持。
在POI的使用过程中,对大多数API User来说经常面临两个问题,这也是 GridExcel 致力解决的问题。
将Excel处理逻辑抽取出来,封装成工具类。
与大多数Java API一样,POI把更多的精力放在高级功能的处理上,比如Formula(公式)、Conditional Formatting(条件格式)、Zoom(缩放)等。对于仅仅做数据导入导出功能的API User,很少使用这些高级特性,这允许API用户对POI的使用进行简单的封装。
无论是读是写,我们都需要解决Excel中的Columns(列)与Java数据对象Fields(字段)的映射关系,将这种映射关系作为参数(Map对象HashMap或LinkedHashMap),传递给工具类。
对于Columns不难理解,它可以是有序的数字或字母,也可以是其它字符串用来作为首行,表示该列数据的含义。
对于Fields,它的处理需要兼容复杂情况,如下:
value == true?完成:失败;
首先想到,也是大多数封装者都在使用的方式是就是 Reflection API ,从上文 函数编程 章节我们了解到,反射重量级,会降低代码的性能,同时对复杂情况的处理支持性不够好。
这种方式可以更好的支持复杂情况,但是反射依然会降低性能,同时注解对数据对象会造成代码侵入,而且对该工具类封装者的其他使用者无疑会增加学习成本。
这种方式也可以很好的支持复杂情况,但是使用匿名内部类的语法显然患有“垂直问题”(这意味着代码需要太多的线条来表达基本概念),太过冗杂。 至于性能,应该也不如直接传递函数来的快吧。
这种方式是基于第5条方法调用的字节码指令 invokeDynamic 实现的,直接传递函数代码块,很好的支持复杂情况,性能较高,代码编写更简单结构更加简洁,而且对数据对象代码零侵入。
内存溢出
或 频繁的Full GC
,该如何解决? POI的使用对我们来说很常见,对下面两个概念应该并不陌生:
但是对于 eventmodel
和 streaming.SXSSFWorkbook
就很少接触了,它们是POI提供的专门用来解决内存占用问题的 low level API
(低级API),使用它们可以读写数据量非常大的Excel,同时可以避免 内存溢出
或 频繁的Full GC
。【 poi.apache.org/components/…
write(OutputStream stream)
方法写出内容时,再直接从临时内存写出到目标 OutputStream
。 SXSSFWorkbook
的使用会产生一些局限性。 实际上POI官网已经给了用户使用示例,而上述工具只是做了自己的封装实现,让使用更方便。
<dependency> <groupId>com.github.liuhuagui</groupId> <artifactId>gridexcel</artifactId> <version>2.2</version> </dependency> 复制代码
GridExcel.java提供了多种静态方法,可以直接使用,具体式例可参考测试代码(提供了测试数据和测试文件):
/** * 业务逻辑处理方式三选一: * 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"))); } 复制代码
Use user model to read excel file. userModel ——
Use event model to read excel file. eventModel ——
Use user model to write excel file. userModel ——
Use API-compatible streaming extension of XSSF to write very large excel file. streaming userModel——