当使用POI打开Excel文件遇到out of memory时该如何处理?
摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。
当我们开发处理Excel文件时,Apache POI 是许多人首选的工具。但是,随着需求的增加、工程复杂,在打开复杂的Excel文件的时候可能会出现一些异常情况。
根据测试,当打开50万个单元格数据的时候,就会遇到OOM(OutOfMemory)的问题;或者当打开包含有20万个合并单元格(包含border或者背景色)的时候,也会遇到OOM(OutOfMemory)的问题。
使用的是WorkbookFactory,直接打开Excel文件,代码如下:
File file = new File("testFile.xlsx");
Workbook workbook = WorkbookFactory.create(file);
//打开文件后进行其他处理
以上代码在处理大型Excel文件时会导致OOM问题的发生。
在网上查了一下,有两个方法:
- 可以把文件转化为CSV然后导入。
- 把Excel文件风格为小的Excel文件,分别构建workbook,然后进行处理。
第一个办法,对于仅导入数据时很有效。但当Excel是有样式的情况时,把Excel转成CSV就会导致样式丢失,所以pass了这个方法。
似乎可以考虑一下第二个办法,把文件分割成多个小文件,分别构建workbook,然后去处理。
于是手动把Excel文件拆分开,把代码简单改了一下,进行测试。
File file = new File("test.xlsx");
File file1 = new File("test1.xlsx");
File file2 = new File("test2.xlsx");
File file3 = new File("test3.xlsx");
File file4 = new File("test4.xlsx");
File file5 = new File("test5.xlsx");
File file6 = new File("test6.xlsx");
Workbook workbook = WorkbookFactory.create(file);
Workbook workbook1 = WorkbookFactory.create(file1);
Workbook workbook2 = WorkbookFactory.create(file2);
Workbook workbook3 = WorkbookFactory.create(file3);
Workbook workbook4 = WorkbookFactory.create(file4);
Workbook workbook5 = WorkbookFactory.create(file5);
Workbook workbook6 = WorkbookFactory.create(file6);
但还是遇到了问题,还是出现了oom的问题,使用的是unit test做的测试,报错内容如下:
...
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.util.Arrays.copyOfRange(Arrays.java:3664)
at java.lang.String.\<init\>(String.java:207)
at com.sun.org.apache.xerces.internal.xni.XMLString.toString(XMLString.java:190)
at com.sun.org.apache.xerces.internal.util.XMLAttributesImpl.getValue(XMLAttributesImpl.java:523)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser\$AttributesProxy.getValue(AbstractSAXParser.java:2321)
...
经过一些尝试,发现是同一时间构建的workbook太多了,当减少到4个时,单元测试就可以正常跑完。
这样来看,POI的问题还真是让人挺头疼。测试的时候,文件是可以知道被分为几个的,但是实际应用时,就没法预测文件的数量。此外根据测试来看,workbook的数量,可能是跟Excel文件的大小相关,这会导致后续开发时可能会遇到更多的问题。
继续网上冲浪,看到除了POI的优化方法,还看到有EasyExcel和GcExcel等其他产品。
简单check了一下,EasyExcel是开源的,主要是对高并发的读写场景做得很好。GcExcel是商业软件,API很全。
那可以分别使用这两个组件验证一下,我们主要想解决的问题有两个:
- 大量数据和样式的Excel文件能一次性打开
- 可以有办法保留样式或者操复制样式
对于问题1,EasyExcel和GcExcel都可以做的很好,没有出现OOM的问题了。代码上两个组件风格不太一样,GcExcel和POI比较相似,是直接构建workbook。POI给的例子是通过注解,更像是反序列化的体验,同时每次读取要写一个监听器,通过监听器处理特殊逻辑。
对于问题2,写了一下UT,代码分别如下:
先看看EasyExcel,
首先EasyExcel需要定义一个Data类,来读取数据。
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {
private String cell1;
private String cell2;
}
定义一个listener类,处理style的逻辑需要在invoke里进行处理,没找到EasyExcel相关的API,还是使用到了POI本身的API来处理样式相关的内容。
@Slf4j
public class DemoListener implements ReadListener\<DemoData\> {
private int rowNum = 0;
private Sheet sheet;
@Override
public void invoke(DemoData data, AnalysisContext context) {
if (sheet == null) {
sheet = (Sheet) context.readSheetHolder().getReadSheet();
}
Row row = sheet.getRow(rowNum);
// 获取第一列
Cell cell0 = row.getCell(0);
CellStyle style0 = cell0.getCellStyle();
// 创建样式对象
Workbook workbook = sheet.getWorkbook();
CellStyle newStyle = workbook.createCellStyle();
// 复制原有样式到新创建的样式对象中
newStyle.cloneStyleFrom(style0);
// TODO: 其他操作
rowNum++;
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
}
从官网看到,在EasyExcel 2.0.0-beta1以后,可以使用extra方法获取批注,超链接,合并单元格信息。但是如果有border或者其他的样式,似乎好像不能用这个方法。
经过简单的测试,问题可以解决,但是样式处理起来还是比较复杂。
对于GcExcel,根据官方文档代码书上很简单。直接基于Range的概念就可以通过set/get方法获取各种样式。
https://www.grapecity.com.cn/developer/grapecitydocuments/excel-java/docs/Features/ApplyStyle

做一下简单的测试吧,用起来很简单,只要理解Excel相关的概念就可以轻松获取到style。
@Test
public void testRepeatCreateObject() throws IOException {
String fileName = "test.xlsx";
Workbook workbook = new Workbook();
workbook.open(fileName);
IWorksheet sheet = workbook.getWorksheets().get(0);
IStyle style = sheet.getRange(0,0).getStyle();
System.out.println("font "+style.getFont().getName());
System.out.println("border "+style.getBorders().getLineStyle().name());
}
至此,整体上看,喜欢使用开源的话,可以选择EasyExcel。EasyExcel提供了反序列化一样的注解方式,读取数据。在数据读取方面很简单。但是在样式处理上,得依赖事件机制去处理,这个还是有一点麻烦的。
如果是做商业项目开发,可以考虑GcExcel。GcExcel在API上十分简单易用,另外在测试中发现,打开文件的速度也快很多,可以降低开发成本。
扩展链接:
当使用POI打开Excel文件遇到out of memory时该如何处理?的更多相关文章
- Java——poi读取Excel文件
1.创建文件流,打开EXCEL文件 FileInputStream excelFile = new FileInputStream(excelPath); XSSFWorkbook workbook ...
- java使用POI操作excel文件,实现批量导出,和导入
一.POI的定义 JAVA中操作Excel的有两种比较主流的工具包: JXL 和 POI .jxl 只能操作Excel 95, 97, 2000也即以.xls为后缀的excel.而poi可以操作Exc ...
- 麦咖啡阻挡正常打开Excel文件
双击打开Excel文件,提示如下图: Excel文件被麦咖啡做阻挡,无法正常打开 处理方案: 过一会儿还是出现此问题,干脆就把缓冲区保护给禁用掉
- C#中的Excel操作【1】——设置Excel单元格的内容,打开Excel文件的一种方式
前言 作为项目管理大队中的一员,在公司里面接触最多的就是Excel文件了,所以一开始就想从Excel入手,学习简单的二次开发,开始自己的编程之路! 程序界面 功能说明 打开文件按钮,可以由使用者指定要 ...
- Java Struts2 POI创建Excel文件并实现文件下载
Java Struts2 POI创建Excel文件并实现文件下载2013-09-04 18:53 6059人阅读 评论(1) 收藏 举报 分类: Java EE(49) Struts(6) 版权声明: ...
- JAVA使用POI读取EXCEL文件的简单model
一.JAVA使用POI读取EXCEL文件的简单model 1.所需要的jar commons-codec-1.10.jarcommons-logging-1.2.jarjunit-4.12.jarlo ...
- VB中后台打开Excel文件实现代码
某些时候需要打开Excel文件来获取或者写入数据,但又不希望跳出打开的Excel文件窗口,可以用下面的代码: Dim eb As New excel.Application, wb as excel. ...
- POI生成EXCEL文件
POI生成EXCEL文件 一.背景 根据指定格式的JSON文件生成对应的excel文件,需求如下 支持多sheet 支持单元格合并 支持插入图片 支持单元格样式可定制 需要 标题(title),表头( ...
- java使用jxl,poi解析excel文件
public interface JavaExcel { /** * 使用jxl写excel文件 */ public void writeJxlExcel(); /** * 使用jxl读excel文件 ...
- 使用poi读写excel文件
使用poi库测试了一下读取excel文件,效果不错,跟大家分享一下. 第一列是数值型,第二列是字符型,代码如下: package poi; import java.io.FileInputStream ...
随机推荐
- 四月二十日java基础知识
1.不可被继承的成员与最终类:在默认情况下,所有的成员变量和成员方法都可以被覆盖,如果父类的成员不希望被子类的成员锁覆盖可以将它们声明为final.如果用final来修饰成员变量,则说明该成员变量是最 ...
- Terraform 系列-什么是 IaC?
系列文章 Terraform 系列文章 前言 聊到 Terraform, 必然绕不开 IaC 这个概念?那么,什么是 IaC? 基本概念 基础架构即代码 (Infrastructure as Code ...
- Rust中的into函数和from函数
1.Rust中的into函数和from函数是做什么用的? into函数是Rust语言中的一个转换函数,它属于Into trait.它可以将一个类型转换为另一个类型.实现了From trait的类型会自 ...
- Redis缓冲区溢出及解决方案
缓冲区(buffer),是内存空间的一部分.也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区. 一.Redis缓冲区溢出影响 在Redis ...
- 5219. 【GDOI2018模拟7.10】B
5219. [GDOI2018模拟7.10]B 题目大意: 考试想法: 正解: 代码: 题目大意: 现在有一个字符串 s s s 当 s [ i ] s[i] s[i]为 I I I时 a n s [ ...
- 如何在Godot中使用ParallaxBackground实现稳定的2d游戏背景[一问随笔]
问题: 我尝试给2d游戏添加静态的背景,当角色运动速度很快时相机的渲染就跟不上角色了,背景会发生这样巨大的位移. 我将Camera2d节点和背景节点绑在一起,但根本无法解决这个问题. 我还尝试制作天空 ...
- Python分支结构之if语句
程序结构 程序三种结构 顺序 循环 分支 分支结构 分支结构基本语法 if 条件表达式: 语句1 语句2 语句3 ...... 条件表达式就是计算结果必须为布尔值的表达式 表达式后面的冒号不能少 注意 ...
- 当Serverless遇到Regionless:现状与挑战
摘要:本文尝试基于分析现有的学术文章,剖析Serverless与Regionless并存时,在性能提升和成本控制两个方向的现状与挑战 本文分享自华为云社区<当Serverless遇到Region ...
- 【Azure 存储服务】Java Storage SDK 调用 uploadWithResponse 代码示例(询问ChatGTP得代码原型后人力验证)
问题描述 查看Java Storage SDK,想找一个 uploadWithResponse 的示例代码,但是通过全网搜索,结果没有任何有帮助的代码.使用最近ChatGPT来寻求答案,得到非常有格 ...
- 前端模拟“多线程”提交Http请求
首先说,javascript没有多线程这样一个说法,我说的只是类似那种效果.其次,不建议使用这种方式解决问题,多线程应该交给后台去做. 但是,如果非要这样用,有什么方法呢? 我在工作中就遇到了这样的问 ...