Java8如何让Excel的读写变得更加简单高效

在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,它的处理需要兼容复杂情况,如下:

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

比如说,想要增加对首行以外的行列做样式扩展 需要java学习路线图的私信笔者“java”领取哦!另外喜欢这篇文章的可以给笔者点个赞同,关注一下,每天都会分享Java相关文章!还有不定时的福利赠送,包括整理的学习资料,面试题,源码等~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容