摘要:本文由葡萄城技术团队于博客园原创并首发。转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。

当我们开发处理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问题的发生。

在网上查了一下,有两个方法:

  1. 可以把文件转化为CSV然后导入。
  2. 把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很全。

那可以分别使用这两个组件验证一下,我们主要想解决的问题有两个:

  1. 大量数据和样式的Excel文件能一次性打开
  2. 可以有办法保留样式或者操复制样式

对于问题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上十分简单易用,另外在测试中发现,打开文件的速度也快很多,可以降低开发成本。

扩展链接:

在服务器端导入导出Excel

如何用C1实现应用程序与微软Excel的交互

中国式复杂报表开发教程(1)—类Excel单维度交叉表

当使用POI打开Excel文件遇到out of memory时该如何处理?的更多相关文章

  1. Java——poi读取Excel文件

    1.创建文件流,打开EXCEL文件 FileInputStream excelFile = new FileInputStream(excelPath); XSSFWorkbook workbook ...

  2. java使用POI操作excel文件,实现批量导出,和导入

    一.POI的定义 JAVA中操作Excel的有两种比较主流的工具包: JXL 和 POI .jxl 只能操作Excel 95, 97, 2000也即以.xls为后缀的excel.而poi可以操作Exc ...

  3. 麦咖啡阻挡正常打开Excel文件

    双击打开Excel文件,提示如下图: Excel文件被麦咖啡做阻挡,无法正常打开 处理方案: 过一会儿还是出现此问题,干脆就把缓冲区保护给禁用掉

  4. C#中的Excel操作【1】——设置Excel单元格的内容,打开Excel文件的一种方式

    前言 作为项目管理大队中的一员,在公司里面接触最多的就是Excel文件了,所以一开始就想从Excel入手,学习简单的二次开发,开始自己的编程之路! 程序界面 功能说明 打开文件按钮,可以由使用者指定要 ...

  5. Java Struts2 POI创建Excel文件并实现文件下载

    Java Struts2 POI创建Excel文件并实现文件下载2013-09-04 18:53 6059人阅读 评论(1) 收藏 举报 分类: Java EE(49) Struts(6) 版权声明: ...

  6. JAVA使用POI读取EXCEL文件的简单model

    一.JAVA使用POI读取EXCEL文件的简单model 1.所需要的jar commons-codec-1.10.jarcommons-logging-1.2.jarjunit-4.12.jarlo ...

  7. VB中后台打开Excel文件实现代码

    某些时候需要打开Excel文件来获取或者写入数据,但又不希望跳出打开的Excel文件窗口,可以用下面的代码: Dim eb As New excel.Application, wb as excel. ...

  8. POI生成EXCEL文件

    POI生成EXCEL文件 一.背景 根据指定格式的JSON文件生成对应的excel文件,需求如下 支持多sheet 支持单元格合并 支持插入图片 支持单元格样式可定制 需要 标题(title),表头( ...

  9. java使用jxl,poi解析excel文件

    public interface JavaExcel { /** * 使用jxl写excel文件 */ public void writeJxlExcel(); /** * 使用jxl读excel文件 ...

  10. 使用poi读写excel文件

    使用poi库测试了一下读取excel文件,效果不错,跟大家分享一下. 第一列是数值型,第二列是字符型,代码如下: package poi; import java.io.FileInputStream ...

随机推荐

  1. Ubuntu系统Flameshot使用问题

    Ubuntu系统Flameshot使用问题 系统:Ubuntu22.04 问题:使用Flameshot,每次都会先截取整个屏幕,提示需要先分享,再使用Flameshot的功能 安装Flameshot ...

  2. 授权普通用户 sudo -i 使其具有root的权限

    1.ssh 秘钥登录授权 将此代码片更名为  adduser.sh 放在linux的 /root 目录下 #!/bin/sh echo 'ssh-key:' $1 echo 'username:' $ ...

  3. 已知n个数的入栈序列,求一共有多少种出栈序列 (卡特兰数)

    已知\(n\)个数的入栈序列,求一共有多少种出栈序列 这个经典问题有两种解法. 解法一: 设\(f(x)\)为\(x\)个数入栈后,再全部出栈的序列数量 假设我们有\(4\)个数\(a,b,c,d\) ...

  4. Spring之丐版IOC实现

    文章目录 IOC控制反转 依赖注入 Bean的自动装配方式 丐版IOC实现 BeanDefinition.java ResourceLoader.java BeanRegister.java Bean ...

  5. 「学习笔记」AC 自动机

    「学习笔记」AC 自动机 点击查看目录 目录 「学习笔记」AC 自动机 算法 问题 思路 代码 例题 Keywords Search 玄武密码 单词 病毒 最短母串 文本生成器 背单词 密码 禁忌 前 ...

  6. 武装你的WEBAPI-OData与DTO

    前面写了很多有关OData使用的文章,很多读者会有疑问,直接将实体对象暴露给最终用户会不会有风险?$expand在默认配置的情况下,数据会不会有泄露风险? 答案是肯定的,由于OData的特性,提供给我 ...

  7. git拉取代码总提示输入密码解决方法

    公司用的gitlab,在项目拉取(git clone)和更新(git pull)的时候,每次都提示输入用户名密码,不胜其烦,解决方法如下: # 首先 git config --global crede ...

  8. Hibernate 基本操作、懒加载以及缓存

    前言 上一篇咱们介绍了 Hibernate 以及写了一个 Hibernate 的工具类,快速入门体验了一波 Hibernate 的使用,我们只需通过 Session 对象就能实现数据库的操作了. 现在 ...

  9. [HUBUCTF 2022 新生赛]help

    题目告诉我们要走迷宫了嘛,那么主要就是找地图: 查壳: 64位,进IDA: 创建地图?跟进去看看: 看看num里装了啥: emm挺长的,有能力的小伙伴可以手搓一个地图,反正我没手搓出来QWQ 再看看判 ...

  10. npm run serve/build 背后的真实操作

    vue CLI 用起来的确很舒服,方便省事,但他经过层层封装很难明白,执行完那个npm run serve/build 后他都干了些什么,甚至不知道整个项目是怎么跑起来的,今天自己抽时间就去瞅瞅,为加 ...