java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架
产品需求
产品经理需要导出一个页面的所有的信息到 EXCEL 文件。
需求分析
对于 excel 导出,是一个很常见的需求。
最常见的解决方案就是使用 poi 直接同步导出一个 excel 文件。
客户体验 & 服务性能
- 客户体验
如果导出的文件比较大,比如几十万条数据,同步导出页面就会卡主,用户无法进行其他操作。
- 服务性能
导出的时候,任务比较耗时就会阻塞主线程。
如果导出的服务是暴露给外部(前后端分离),这种大量的数据传输十分消耗性能。
解决方案
使用异常处理导出请求,后台 MQ 通知自己进行处理。
MQ 消费之后,多线程处理 excel 文件导出,生成文件后上传到 FTP 等文件服务器。
前端直接查询并且展现对应的任务执行列表,去 FTP 等文件服务器下载文件即可。
EXCEL 导出需要考虑的问题
OOM
正常的 poi 在处理比较大的 excel 的时候,会出现内存溢出。
网上的解决方案也比较多。
比如官方的 SXSSF (Since POI 3.8 beta3) 解决方式。
或者使用封装好的包
原理都是强制使用 xssf 版本的Excel。
你也可以使用 easyexcel,当然这个注释文档有些欠缺,而且设计的比较复杂,不是很推荐。
我这里使用的是 hutool BigExcelWriter,
懒得自己再写一遍。
FULL GC
如果一次查询 100W 条数据库,然后把这些信息全部加载到内存中,是不可取的。
建议有2个:
限制每一次分页的数量。比如一次最多查询 1w 条。分成 100 次查询。(必须)
限制查询得总条数。比如限制为最多 10W 条。(根据实际情况选择)
虽然使用者提出要导出类似于 3 个月的所有信息,但是数量太多,毫无意义。(提出者自己可能体会不到)
尽量避免 FULL-GC 的情况发生,因为目前的所有方式对于 excel 的输出流都会占用内存,100W 条很容易导致 FULL-GC。
数据库的压力
去数据库读取的时候一定要记得分页,免得给数据库太大的压力。
一次读取太多,也会导致内存直线上升。
比如 100W 条数据,则分成 100 次去数据库读取。
网络传输
传统的 excel 导出,都是前端一个请求,直接 HTTP 同步返回。导出 100W 条,就在那里傻等。
这客户体验不友好,而且网络传输,系统占用多种问题。
建议使用异步处理的方式,将文件上传到文件服务器。前端直接去文件服务器读取。
编程的便利性
对于上面提到的工具,比如 Hutool,在表头的处理方面没法很方便的统一。
你可以自己定义类似于 easypoi/easyexcel 中的注解,自己反射解析。
然后统一处理表头即可。
IExcel 方便优雅的 excel 框架
特性
OO 的方式操作 excel,编程更加方便优雅。
sax 模式读取,SXSS 模式写入。避免 excel 大文件 OOM。
基于注解,编程更加灵活。
写入可以基于对象列表,也可以基于 Map,实际使用更加方便。
设计简单,注释完整。方便大家学习改造。
后期特性
读取跳过空白行
excel 样式相关的注解开发
创作缘由
实际工作和学习中,apache poi 操作 excel 过于复杂。
近期也看了一些其他的工具框架:
easypoi
easyexcel
hutool-poi
都或多或少难以满足自己的实际需要,于是就自己写了一个操作 excel 导出的工具。
快速开始
引入 Jar
使用 maven 管理。
<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>iexcel</artifactId>
<version>0.0.2</version>
</dependency>
定义对象
你可以直接参考 ExcelUtilTest.java
定义一个需要写入/读取的 excel 对象。
- ExcelFieldModel.java
只有声明了 @ExcelField 的属性才会被处理,使用说明:@ExcelField
public class ExcelFieldModel {
@ExcelField
private String name;
@ExcelField(headName = "年龄")
private String age;
@ExcelField(mapKey = "EMAIL", writeRequire = false, readRequire = false)
private String email;
@ExcelField(mapKey = "ADDRESS", headName = "地址", writeRequire = true)
private String address;
//getter and setter
}
写入例子
IExcelWriter 的实现
IExcelWriter 有几个实现类,你可以直接 new 或者借助 ExcelUtil 类去创建。
| IExcelWriter 实现类 | ExcelUtil 如何创建 | 说明 |
|---|---|---|
| HSSFExcelWriter | ExcelUtil.get03ExcelWriter() | 2003 版本的 excel |
| XSSFExcelWriter | ExcelUtil.get07ExcelWriter() | 2007 版本的 excel |
| SXSSFExcelWriter | ExcelUtil.getBigExcelWriter() | 大文件 excel,避免 OOM |
写入到 2003
- excelWriter03Test()
一个将对象列表写入 2003 excel 文件的例子。
/**
* 写入到 03 excel 文件
*/
@Test
public void excelWriter03Test() {
// 待生成的 excel 文件路径
final String filePath = "excelWriter03.xls";
// 对象列表
List<ExcelFieldModel> models = buildModelList();
try(IExcelWriter excelWriter = ExcelUtil.get03ExcelWriter();
OutputStream outputStream = new FileOutputStream(filePath)) {
// 可根据实际需要,多次写入列表
excelWriter.write(models);
// 将列表内容真正的输出到 excel 文件
excelWriter.flush(outputStream);
} catch (IOException e) {
throw new ExcelRuntimeException(e);
}
}
- buildModelList()
/**
* 构建测试的对象列表
* @return 对象列表
*/
private List<ExcelFieldModel> buildModelList() {
List<ExcelFieldModel> models = new ArrayList<>();
ExcelFieldModel model = new ExcelFieldModel();
model.setName("测试1号");
model.setAge("25");
model.setEmail("123@gmail.com");
model.setAddress("贝克街23号");
ExcelFieldModel modelTwo = new ExcelFieldModel();
modelTwo.setName("测试2号");
modelTwo.setAge("30");
modelTwo.setEmail("125@gmail.com");
modelTwo.setAddress("贝克街26号");
models.add(model);
models.add(modelTwo);
return models;
}
一次性写入到 2007 excel
有时候列表只写入一次很常见,所有就简单的封装了下:
/**
* 只写入一次列表
* 其实是对原来方法的简单封装
*/
@Test
public void onceWriterAndFlush07Test() {
// 待生成的 excel 文件路径
final String filePath = "onceWriterAndFlush07.xlsx";
// 对象列表
List<ExcelFieldModel> models = buildModelList();
// 对应的 excel 写入对象
IExcelWriter excelWriter = ExcelUtil.get07ExcelWriter();
// 只写入一次列表
ExcelUtil.onceWriteAndFlush(excelWriter, models, filePath);
}
读取例子
excel 读取时会根据文件名称判断是哪个版本的 excel。
IExcelReader 的实现
IExcelReader 有几个实现类,你可以直接 new 或者借助 ExcelUtil 类去创建。
| IExcelReader 实现类 | ExcelUtil 如何创建 | 说明 |
|---|---|---|
| ExcelReader | ExcelUtil.getExcelReader() | 小文件的 excel 读取实现 |
| Sax03ExcelReader | ExcelUtil.getBigExcelReader() | 大文件的 2003 excel 读取实现 |
| Sax07ExcelReader | ExcelUtil.getBigExcelReader() | 大文件的 2007 excel 读取实现 |
excel 读取的例子
/**
* 读取测试
*/
@Test
public void readWriterTest() {
File file = new File("excelWriter03.xls");
IExcelReader<ExcelFieldModel> excelReader = ExcelUtil.getExcelReader(file);
List<ExcelFieldModel> models = excelReader.readAll(ExcelFieldModel.class);
System.out.println(models);
}
ExcelField 注解说明
@ExcelField 的属性说明如下:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| mapKey | String | "" |
仅用于生成的入参为 map 时,会将 map.key 对应的值映射到 bean 上。如果不传:默认使用当前字段名称 |
| headName | String | "" |
excel 表头字段名称,如果不传:默认使用当前字段名称 |
| writeRequire | boolean | true | excel 文件是否需要写入此字段 |
| readRequire | boolean | true | excel 文件是否读取此字段 |
IExcelWriter 接口说明
/**
* 写出数据,本方法只是将数据写入Workbook中的Sheet,并不写出到文件<br>
* <p>
* data中元素支持的类型有:
* <pre>
* 1. Bean,既元素为一个Bean,第一个Bean的字段名列表会作为首行,剩下的行为Bean的字段值列表,data表示多行 <br>
* </pre>
* @param data 数据
* @return this
*/
IExcelWriter write(Collection<?> data);
/**
* 写出数据,本方法只是将数据写入Workbook中的Sheet,并不写出到文件<br>
* 将 map 按照 targetClass 转换为对象列表
* 应用场景: 直接 mybatis mapper 查询出的 map 结果,或者其他的构造结果。
* @param mapList map 集合
* @param targetClass 目标类型
* @return this
*/
IExcelWriter write(Collection<Map<String, Object>> mapList, final Class<?> targetClass);
/**
* 将Excel Workbook刷出到输出流
*
* @param outputStream 输出流
* @return this
*/
IExcelWriter flush(OutputStream outputStream);
指定 sheet
创建 IExcelWriter 的时候,可以指定 sheet 的下标或者名称。来指定写入的 sheet。
是否包含表头
创建 IExcelWriter 的后,可以调用 excelWriter.containsHead(bool) 指定是否生成 excel 表头。
IExcelReader 接口说明
/**
* 读取当前 sheet 的所有信息
* @param tClass 对应的 javabean 类型
* @return 对象列表
*/
List<T> readAll(Class<T> tClass);
/**
* 读取指定范围内的
* @param tClass 泛型
* @param startIndex 开始的行信息(从0开始)
* @param endIndex 结束的行信息
* @return 读取的对象列表
*/
List<T> read(Class<T> tClass, final int startIndex, final int endIndex);
指定 sheet
创建 IExcelReader 的时候,可以指定 sheet 的下标或者名称。来指定读取的 sheet。
注意:大文件 sax 读取模式,只支持指定 sheet 的下标。
是否包含表头
创建 IExcelReader 的后,可以调用 excelReader.containsHead(bool) 指定是否读取 excel 表头。
拓展阅读
java 导出 excel 最佳实践,java 大文件 excel 避免OOM(内存溢出) excel 工具框架的更多相关文章
- Atitit. 解压缩zip文件 的实现最佳实践 java c# .net php
Atitit. 解压缩zip文件 的实现最佳实践 java c# .net php 1. Jdk zip 跟apache ant zip 1 2. Apache Ant包进行ZIP文件压缩,upzip ...
- paip.复制文件 文件操作 api的设计uapi java python php 最佳实践
paip.复制文件 文件操作 api的设计uapi java python php 最佳实践 =====uapi copy() =====java的无,要自己写... ====php copy ...
- atitit.压缩算法 ZLib ,gzip ,zip 最佳实践 java .net php
atitit.压缩算法 ZLib ,gzip ,zip 最佳实践 java .net php 1. 压缩算法的归类::: 纯算法,带归档算法 1 2. zlib(适合字符串压缩) 1 3. gz ...
- atitit.人脸识别的应用场景and使用最佳实践 java .net php
atitit.人脸识别的应用场景and使用最佳实践 java .net php 1. 人脸识别的应用场景 1 2. 框架选型 JNI2OpenCV.dll and JavaCV 1 3. Url ap ...
- atitit.人脸识别的应用场景and使用最佳实践 java .net php
atitit.人脸识别的应用场景and使用最佳实践 java .net php 1. 人脸识别的应用场景1 2. 标准化的api1 3. 框架选型 JNI2OpenCV.dll and JavaCV ...
- Java 网络编程最佳实践(转载)
http://yihongwei.com/2015/09/remoting-practice/ Java 网络编程最佳实践 Sep 10, 2015 | [Java, Network] 1. 通信层 ...
- 避免Java中NullPointerException的Java技巧和最佳实践
Java中的NullPointerException是我们最经常遇到的异常了,那我们到底应该如何在编写代码是防患于未然呢.下面我们就从几个方面来入手,解决这个棘手的问题吧. 值得庆幸的是,通过应用 ...
- 使用DataStax Java驱动程序的最佳实践
引言 如果您想开始建立自己的基于Cassandra的Java程序,欢迎! 也许您已经参加过我们精彩的DataStax Academy课程或开发者大会,又或者仔细阅读过Cassandra Java驱动的 ...
- java内存溢出分析工具
http://www.cnblogs.com/preftest/archive/2011/12/08/2281322.html java内存溢出分析工具:jmap使用实战 在一次解决系统tomcat老 ...
随机推荐
- app内嵌vue h5,安卓和ios拦截H5点击事件
安卓和ios拦截h5点击事件,这个函数事件必须是暴漏在window下的 安卓和ios拦截普通h5函数: <div onclick = "show(),window.android.sh ...
- springboot+mybatis-puls利用swagger构建api文档
项目开发常采用前后端分离的方式.前后端通过API进行交互,在Swagger UI中,前后端人员能够直观预览并且测试API,方便前后端人员同步开发. 在SpringBoot中集成swagger,步骤如下 ...
- jquery学习-document.ready和document.onload区别
$(function(){}) 和$(document).ready(function(){}的作用一样,表示在document树加载完之后执行一个函数. $(document).onload(fun ...
- AssetBundle打包-----BuildPipeline的应用
打包思路:确定要打包资源的路径.和打包的输出路径(一般为S路径),把存放资源的路径使用递归进行遍历,获取所有资源,文件类型的资源可以通过File拷贝或IO写到输出路径,其他资源的打包通过AssetBu ...
- javaee设计模型简介
(一)五种模式 1.单例模式 在某些情况下,有些对象只需要一个就可以了,即每个类只需要一个实例.例如,一台计算机上的可以连接多台打印机,但是该计算机上的打印程序只能有一个,这里就可以通过单例模式来避免 ...
- sqlserver数据库知识点总结(转)
- 前端基础:canvas概述
1:并不是所有的浏览器都支持canvas,若是不支持则浏览器会弹出 <canvas>Update your browser to enjoy canvas</canvas> 浏 ...
- C++——STL内存清除
1.vector元素的清除 看代码.在vector中添加若干元素,然后clear() #include<iostream> #include<list> #include< ...
- 【转】Python高级知识点总结
一.可迭代对象.迭代器对象和生成器 像list, tuple等这些序列是可以使用for...in ...语句来进行遍历输出的.这是为什么呢?这就需要知道可迭代对象(Iterable).迭代器对象(It ...
- Dev GridControl数据修改后实时更新数据源(转)
1: /// <summary> 2: /// 嵌入的ComboxEdit控件选择值变化事件 3: /// </summary> 4: /// <param nam ...