环境: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>

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();}


由于整行数据已被缓存,你可以随机访问行内的单元格。但是,无法随机访问行。由于这是流式实现,因此任何时候内存中仅保留少量行。
默认情况下,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)更高效。
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);
与共享字符串(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);
<dependency><groupId>com.github.pjfanning</groupId><artifactId>poi-shared-strings</artifactId><version>2.10.0</version></dependency>
@RequestBody 不止读 JSON!这 7 种格式你肯定没用全
零信任网关:扩展 @RequestMapping 实现动态 API 管控
@RequestBody已弃用!Spring Boot 一个注解实现任意JSON的读取
Spring Boot 获取所有 Controller 接口的4种方法
强大!Spring Boot 使用强大的@Formula注解简化查询
Spring Boot + FFmpeg 实现真正的实时视频流(HLS)
告别多源查询混乱!Spring Boot + Calcite 实现跨库查询
弃用@Value!Spring Boot 自定义 @PackValue 注解实现万能资源注入

夜雨聆风