欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

基于Java8实现提高Excel读写效率

程序员文章站 2022-03-28 17:41:44
在poi的使用过程中,对大多数api user来说经常面临两个问题,这也是gridexcel致力解决的问题。问题1. 仅使用简单的导入导出功能,但每次业务的数据对象结构不同,需要重新编写处理方法,很麻...

在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

解决途径

基于java函数编程(lambda),支持流式api,使用环境java1.8或更高,学习成本:

lambda

基于反射+注解+监听器,使用环境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:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

相关标签: Java Excel 读写