乐于分享
好东西不私藏

告别 OOM!开源流式读取超大 Excel,内存占用低到离谱

告别 OOM!开源流式读取超大 Excel,内存占用低到离谱
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!
🎉🎉《Spring Boot实战案例合集》目前已更新229个案例,我们将持续不断的更新。文末有电子书目录。→ 现在就订阅合集

环境:Spring Boot 3.5.0


1. 简介

在日常业务开发中,超大 Excel 文件导入是极为常见的需求。传统 POI 读取方式会将整个文件加载至内存,面对十万、百万级数据时极易出现内存溢出、程序卡顿甚至服务崩溃等问题,严重影响系统稳定性。为解决这一痛点,本文介绍一个开源 POI 组件库(Excel Streaming Reader),它低内存占用的流式读取方案,无需加载整个文件,既保证读取效率,又能极致控制内存消耗,轻松应对各类超大 Excel 导入场景。

Excel Streaming Reader

这是 monitorjbl/excel-streaming-reader 的分支。此实现支持 Apache POI 5.x,且仅支持 Java 8 及以上版本。v2.3.x 支持 POI 4.x。

引入依赖

<dependency>  <groupId>com.github.pjfanning</groupId>  <artifactId>excel-streaming-reader</artifactId>  <version>5.2.0</version></dependency>
2.实战案例
2.1 读取50w数据
该开源库读取数据非常的简单,只需要几行代码,如下示例:
准备如下excel数据(50w)
流式读取:
try (// 读取Excel文件的输入流 InputStream is = new FileInputStream(new File("d:/users.xlsx"));    // 构建流式读取器(专门用于读取大Excel文件,低内存占用)     Workbook workbook = StreamingReader.builder()            .rowCacheSize(100)    // 内存中保留的行数(默认值为10)            .bufferSize(4096)     // 读取输入流到文件时使用的缓冲区大小(单位:字节,默认值为1024)            .open(is)) {  Sheet sheet = workbook.getSheetAt(0) ;  for (Row r : sheet) {    if (r.getRowNum() == 0) {      continue ;    }    for (Cell c : r) {      System.err.print(c.getStringCellValue() + "\t") ;    }    System.err.println() ;    TimeUnit.MILLISECONDS.sleep(500) ;  }catch (Exception e) {  e.printStackTrace();}
运行该程序后,内存占用情况(控制台不停的输出):

由于整行数据已被缓存,你可以随机访问行内的单元格。但是,无法随机访问行。由于这是流式实现,因此任何时候内存中仅保留少量行。

2.2 临时文件共享字符串

默认情况下,xlsx 文件中的 /xl/sharedStrings.xml 共享字符串数据会常驻内存,这可能会引发内存相关问题。

你可以使用 setUseSstTempFile(true) 配置项,将该数据存储到临时文件(基于 H2 MVStore 引擎)中;如果你担心原始数据以明文形式存储在临时文件里,还可以额外启用 setEncryptSstTempFile(true) 配置项对临时文件进行加密。

InputStream is = ... ;// 构建流式读取器(专门用于超大Excel低内存读取)Workbook workbook = StreamingReader.builder()    // 设置要使用的共享字符串表(SST)类型。默认值为 POI_READ_ONLY。    .setSharedStringsImplementationType(SharedStringsImplementationType.TEMP_FILE_BACKED)    // 启用对共享字符串表临时文件的加密。仅当 setUseSstTempFile 设置为 true 时此配置才生效。    // 默认情况下,临时文件不加密。但启用该选项可能会降低共享字符串数据的处理速度。    .setEncryptSstTempFile(false)    // 是否解析富文本共享字符串和批注的完整格式数据。仅当启用了临时文件共享字符串表和 / 或批注表支持时,此配置才生效。    // 默认值为 false。若未使用临时文件支持,无论如何都会返回富文本的完整格式数据。    .setFullFormatRichText(true)    .open(is);

有一种基于映射的实现方案,既能避免使用临时文件,又比 Apache POI 的默认方案(SharedStringsImplementationType.CUSTOM_MAP_BACKED)更高效。

2.3 读取非常大的Excel

excel-streaming-reader 在底层使用了一些 Apache POI 代码。该代码在处理 xlsx 文件时使用内存和/或临时文件来存储临时数据。对于非常大的文件,你可能会倾向于使用临时文件。

使用 StreamingReader.builder() 时,不要将 setAvoidTempFiles(true) (即,不设置避免使用临时文件)。你还应该考虑调整 POI 设置。特别是,考虑设置以下属性:

import org.apache.poi.openxml4j.util.* ;import org.apache.poi.openxml4j.opc.* ;ZipInputStreamZipEntrySource.setThresholdBytesForTempFiles(16384); //16KBZipPackage.setUseTempFilePackageParts(true);
2.4 临时文件注释

与共享字符串(shared strings)一样,注释存储在 xlsx 文件的独立部分中,并且默认情况下,excel-streaming-reader 不会读取这些注释。你可以对 excel-streaming-reader 进行配置以读取这些注释,并选择在读取 xlsx 文件时是将它们存储在内存中还是临时文件中。

Workbook workbook = StreamingReader.builder()    .setReadComments(true)    .setCommentsImplementationType(CommentsImplementationType.TEMP_FILE_BACKED)    .setEncryptCommentsTempFile(false)    // 如果你同时还需要(获取)富文本格式以及文本(内容)    .setFullFormatRichText(true)    .open(is);
注意,此示例与上面(2.2)都需要引入如下的依赖:
<dependency>  <groupId>com.github.pjfanning</groupId>  <artifactId>poi-shared-strings</artifactId>  <version>2.10.0</version></dependency>
以上是本篇文章的全部内容,如对你有帮助帮忙点赞+转发+收藏
不再裸奔!Spring Boot 新一代安全加密方案来了

@RequestBody 不止读 JSON!这 7 种格式你肯定没用全

别再用AOP做限流了!这才是高并发下的王者解法

零信任网关:扩展 @RequestMapping 实现动态 API 管控

@RequestBody已弃用!Spring Boot 一个注解实现任意JSON的读取

Spring Boot 获取所有 Controller 接口的4种方法

强大!Spring Boot 使用强大的@Formula注解简化查询

Spring Boot + FFmpeg 实现真正的实时视频流(HLS)

告别多源查询混乱!Spring Boot + Calcite 实现跨库查询

请不要自己写!Google开源一个注解自动生成重复模板代码

弃用@Value!Spring Boot 自定义 @PackValue 注解实现万能资源注入