Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便

easyexcel核心功能

  • 读任意大小的03、07版Excel不会OOM
  • 读Excel自动通过注解,把结果映射为java模型
  • 读Excel支持多sheet
  • 读Excel时候是否对Excel内容做trim()增加容错
  • 写小量数据的03版Excel(不要超过2000行)
  • 写任意大07版Excel不会OOM
  • 写Excel通过注解将表头自动写入Excel
  • 写Excel可以自定义Excel样式 如:字体,加粗,表头颜色,数据内容颜色
  • 写Excel到多个不同sheet
  • 写Excel时一个sheet可以写多个Table
  • 写Excel时候自定义是否需要写表头

快速使用

1. JAR包依赖

使用前最好咨询下最新版,或者到mvn仓库搜索一下easyexcel的最新版

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>{latestVersion}</version>
</dependency>

2. 读取Excel

使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。

无java模型直接把excel解析的每行结果以List返回 在ExcelListener获取解析结果
读excel代码示例如下:


@Test
public void testExcel2003NoModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行结果在listener中处理
ExcelListener listener = new ExcelListener(); ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener);
excelReader.read();
} catch (Exception e) { } finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

ExcelListener示例代码如下:


/* 解析监听器,
* 每解析一行会回调invoke()方法。
* 整个excel解析结束会执行doAfterAllAnalysed()方法
*
* 下面只是我写的一个样例而已,可以根据自己的逻辑修改该类。
* @author jipengfei
* @date 2017/03/14
*/
public class ExcelListener extends AnalysisEventListener { //自定义用于暂时存储data。
//可以通过实例获取该值
private List<Object> datas = new ArrayList<Object>();
public void invoke(Object object, AnalysisContext context) {
System.out.println("当前行:"+context.getCurrentRowNum());
System.out.println(object);
datas.add(object);//数据存储到list,供批量处理,或后续自己业务逻辑处理。
doSomething(object);//根据自己业务做处理
}
private void doSomething(Object object) {
//1、入库调用接口
}
public void doAfterAllAnalysed(AnalysisContext context) {
// datas.clear();//解析结束销毁不用的资源
}
public List<Object> getDatas() {
return datas;
}
public void setDatas(List<Object> datas) {
this.datas = datas;
}
}

有java模型映射
java模型写法如下:

public class LoanInfo extends BaseRowModel {
@ExcelProperty(index = 0)
private String bankLoanId; @ExcelProperty(index = 1)
private Long customerId; @ExcelProperty(index = 2,format = "yyyy/MM/dd")
private Date loanDate; @ExcelProperty(index = 3)
private BigDecimal quota; @ExcelProperty(index = 4)
private String bankInterestRate; @ExcelProperty(index = 5)
private Integer loanTerm; @ExcelProperty(index = 6,format = "yyyy/MM/dd")
private Date loanEndDate; @ExcelProperty(index = 7)
private BigDecimal interestPerMonth; @ExcelProperty(value = {"一级表头","二级表头"})
private BigDecimal sax;
}

@ExcelProperty(index = 3)数字代表该字段与excel对应列号做映射,也可以采用 @ExcelProperty(value = {“一级表头”,”二级表头”})用于解决不确切知道excel第几列和该字段映射,位置不固定,但表头的内容知道的情况。

    @Test
public void testExcel2003WithReflectModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行结果在listener中处理
AnalysisEventListener listener = new ExcelListener(); ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener); excelReader.read(new Sheet(1, 2, LoanInfo.class));
} catch (Exception e) { } finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} }

带模型解析与不带模型解析主要在构造new Sheet(1, 2, LoanInfo.class)时候包含class。Class需要继承BaseRowModel暂时BaseRowModel没有任何内容,后面升级可能会增加一些默认的数据。

3. 生成Excel

每行数据是List无表头

        OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
writer.write(getListString(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}

每行数据是一个java模型有表头—-表头层级为一

生成Excel格式如下图:

模型写法如下:

public class ExcelPropertyIndexModel extends BaseRowModel {

    @ExcelProperty(value = "姓名" ,index = 0)
private String name; @ExcelProperty(value = "年龄",index = 1)
private String age; @ExcelProperty(value = "邮箱",index = 2)
private String email; @ExcelProperty(value = "地址",index = 3)
private String address; @ExcelProperty(value = "性别",index = 4)
private String sax; @ExcelProperty(value = "高度",index = 5)
private String heigh; @ExcelProperty(value = "备注",index = 6)
private String last;
}

@ExcelProperty(value = “姓名”,index = 0) value是表头数据,默认会写在excel的表头位置,index代表第几列。

    @Test
public void test1() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/78.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0,ExcelPropertyIndexModel.class);
writer.write(getData(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

每行数据是一个java模型有表头—-表头层级为多层级

生成Excel格式如下图:

java模型写法如下:

public class MultiLineHeadExcelModel extends BaseRowModel {

    @ExcelProperty(value = {"表头1","表头1","表头31"},index = 0)
private String p1; @ExcelProperty(value = {"表头1","表头1","表头32"},index = 1)
private String p2; @ExcelProperty(value = {"表头3","表头3","表头3"},index = 2)
private int p3; @ExcelProperty(value = {"表头4","表头4","表头4"},index = 3)
private long p4; @ExcelProperty(value = {"表头5","表头51","表头52"},index = 4)
private String p5; @ExcelProperty(value = {"表头6","表头61","表头611"},index = 5)
private String p6; @ExcelProperty(value = {"表头6","表头61","表头612"},index = 6)
private String p7; @ExcelProperty(value = {"表头6","表头62","表头621"},index = 7)
private String p8; @ExcelProperty(value = {"表头6","表头62","表头622"},index = 8)
private String p9;
}

写Excel写法同上,只需将ExcelPropertyIndexModel.class改为MultiLineHeadExcelModel.class

一个Excel多个sheet写法

    @Test
public void test1() throws FileNotFoundException { OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
writer.write(getListString(), sheet1); //写第二个sheet sheet2 模型上打有表头的注解,合并单元格
Sheet sheet2 = new Sheet(2, 3, MultiLineHeadExcelModel.class, "第二个sheet", null);
sheet2.setTableStyle(getTableStyle1());
writer.write(getModeldatas(), sheet2); //写sheet3 模型上没有注解,表头数据动态传入
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Sheet sheet3 = new Sheet(3, 1, NoAnnModel.class, "第三个sheet", head);
writer.write(getNoAnnModels(), sheet3);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

一个sheet中有多个表格

    @Test
public void test2() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false); //写sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
Table table1 = new Table(1);
writer.write(getListString(), sheet1, table1);
writer.write(getListString(), sheet1, table1); //写sheet2 模型上打有表头的注解
Table table2 = new Table(2);
table2.setTableStyle(getTableStyle1());
table2.setClazz(MultiLineHeadExcelModel.class);
writer.write(getModeldatas(), sheet1, table2); //写sheet3 模型上没有注解,表头数据动态传入,此情况下模型field顺序与excel现实顺序一致
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Table table3 = new Table(3);
table3.setHead(head);
table3.setClazz(NoAnnModel.class);
table3.setTableStyle(getTableStyle2());
writer.write(getNoAnnModels(), sheet1, table3);
writer.write(getNoAnnModels(), sheet1, table3); writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

4. 测试数据分析

从上面的性能测试可以看出easyexcel在解析耗时上比poiuserModel模式弱了一些。主要原因是我内部采用了反射做模型字段映射,中间我也加了cache,但感觉这点差距可以接受的。但在内存消耗上差别就比较明显了,easyexcel在后面文件再增大,内存消耗几乎不会增加了。但poi userModel就不一样了,简直就要爆掉了。想想一个excel解析200M,同时有20个人再用估计一台机器就挂了。

5. 百万数据解析对比

easyexcel解析百万数据内存图如下:

poi解析百万数据内存图如下:

从上面两图可以看出,easyexcel解析时内存消耗很少,最多消耗不到50M;POI解析过程中直接飘升到1.5G左右,系统内存耗尽,程序挂掉。

原文地址:https://www.itsleuth.cn/archives/javatool001

GitHub地址:https://github.com/alibaba/easyexcel

阿里巴巴excel工具easyexcel 助你快速简单避免OOM的更多相关文章

  1. 阿里出品Excel工具EasyExcel使用小结

    前提 笔者做小数据和零号提数工具人已经有一段时间,服务的对象是运营和商务的大佬,一般要求导出的数据是Excel文件,考虑到初创团队机器资源十分有限的前提下,选用了阿里出品的Excel工具EasyExc ...

  2. Excel解析工具easyexcel全面探索

    1. Excel解析工具easyexcel全面探索 1.1. 简介 之前我们想到Excel解析一般是使用POI,但POI存在一个严重的问题,就是非常消耗内存.所以阿里人员对它进行了重写从而诞生了eas ...

  3. 尝试做一个.NET简单、高效、避免OOM的Excel工具

    Github : https://github.com/shps951023/MiniExcel 简介 我尝试做一个.NET简单.高效.避免OOM的Excel工具 目前主流框架大多将资料全载入到记忆体 ...

  4. Excel解析easyexcel工具类

    Excel解析easyexcel工具类 easyexcel解决POI解析Excel出现OOM <!-- https://mvnrepository.com/artifact/com.alibab ...

  5. 【原创】.NET读写Excel工具Spire.Xls使用(1)入门介绍

    在.NET平台,操作Excel文件是一个非常常用的需求,目前比较常规的方法有以下几种: 1.Office Com组件的方式:这个方式非常累人,微软的东西总是这么的复杂,使用起来可能非常不便,需要安装E ...

  6. 【原创】.NET读写Excel工具Spire.Xls使用(4)对数据操作与控制

                  本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p/4288836.html .NET读写Excel工具Spire.Xls使用文章 ...

  7. .NET读写Excel工具Spire.Xls使用(1)入门介绍

    原文:[原创].NET读写Excel工具Spire.Xls使用(1)入门介绍 在.NET平台,操作Excel文件是一个非常常用的需求,目前比较常规的方法有以下几种: 1.Office Com组件的方式 ...

  8. 【待考察】Appium使用技巧,助你快速入门移动端自动化!

    Appium使用技巧,助你快速入门移动端自动化! 原创: 柠檬班superman 柠檬班软件测试 1月4日 关注并置顶[柠檬班]的小哥哥小姐姐 “猪”年行大运 说说最近研究移动端的自动化 移动端的自动 ...

  9. 一行代码完成 Java的 Excel 读写--easyexcel

    最近我在 Github 上查找一个可以快速开发 excel 导入导出工具,偶然发现由阿里开发 easyexcel 开源项目,尝试使用后感觉这款工具挺不错的,下面分享一下我的 easyexcel 案例使 ...

随机推荐

  1. remove方法

    1.jQuery的remove()方法 http://www.365mini.com/page/jquery-remove.htm ①返回值是jquery对象本身 所以可以做删除再添加的操作 // 移 ...

  2. python之分析decode、encode、unicode编码转换

    decode()方法使用注册编码的编解码器的字符串进行解码.它默认为默认的字符串编码.decode函数可以将一个普通字符串转换为unicode对象.decode是将普通字符串按照参数中的编码格式进行解 ...

  3. testng生成自定义html报告

    转自:https://blog.csdn.net/kdslkd/article/details/51198433 testng原生的或reportng的报告总有些不符合需要,尝试生成自定义测试报告,用 ...

  4. 如何修改Tomcat默认端口?

    修改的原因: 关于8080端口:8080端口同80端口,是被用于WWW代理服务的,可以实现网页浏览,经常在访问某个网站或使用代理服务器的时候,会加上":8080"端口号.另外Apa ...

  5. 关于linux下部署JavaWeb项目,nginx负责静态资源访问,tomcat负责处理动态请求的nginx配置

    1.项目的运行环境 linux版本 [root@localhost ~]# cat /proc/version Linux version -.el6.x86_64 (mockbuild@x86-.b ...

  6. Java基础-方法重载和方法重写的区别

    什么是java方法重载 (1) 方法重载是让类以统一的方式处理不同类型数据的一种手段.多个同名函数同时存在,具有不同的参数个数/类型. 重载Overloading是一个类中多态性的一种表现. (2)  ...

  7. Android+appium +python 点击坐标tap方法的封装

    当常使用的查找点击元素的方法name.id.classname等无法使用时,我们将会采取坐标的点击来实现操作,同样存在一个问题,当手机的分辨率.屏幕大小不一致时,坐标的定位也会不同,因此将采用相对坐标 ...

  8. 领域驱动设计学习之路—DDD的原则与实践

    本文是我学习Scott Millett & Nick Tune编著的<领域驱动设计模式.原理与实践>一书的学习笔记,一共会分为4个部分如下,此文为第1部分: ① 领域驱动设计的原则 ...

  9. 基于 ReactJS 开发简单的可视化业务编辑器 01

    线上可以看的,跟github上的代码不一样的:https://whensea.com/wfd/ 程序中经常有一些业务需要定制化,我定制化这些业务的方式主要是基于工作流.配置等方式.由于个人水平限制并不 ...

  10. 【转】AB实验设计思路及实验落地

    这篇文章会讨论: 在什么情况下需要做 AB 实验 从产品/交互角度,如何设计一个实验 前端工程师如何打点 如何统计数据,并保证数据准确可信 如何分析实验数据,有哪些数据需要重点关注 附:如何搭建前端实 ...