当前位置: 首页 > news >正文

openpyxl 流式读取xlsx文件(read_only=true)读不到sheet页中所有行

 问题

最近用openpyxl读取一个xlsx文件时,文件sheet页中明明有多行数据,就是读取不到,仅仅读取了最前面的一行数据。定位发现openpyxl 流式读取xlsx文件(read_only=true,代码示例如下)时,

from openpyxl import load_workbook# 以只读模式加载工作簿
wb = load_workbook(filename='example.xlsx', read_only=True)# 选择要读取的工作表,这里假设选择第一个工作表
sheet = wb.active# 逐行遍历工作表中的数据
for row in sheet.rows:data_row = [cell.value for cell in row]print(data_row)# 关闭工作簿(虽然在这种模式下,openpyxl会自动管理资源,但显式关闭是个好习惯)
wb.close()

首先会解析 xlsx文件名\xl\worksheets 目录下的*.xml文件中的dimension参数,根据dimension参数判断sheet页有多少行内容,然后按照这个行数信息逐行读取sheet页内容。出问题的xlsx文件用java的poi包流式写出的,使用了SXSSFSheet的flushRows()方法(无参数)手动刷新缓存的数据到磁盘,导致dimension没有更新。

PS: xlsx文件其实是一个压缩包,可以将文件扩展名改成zip,进行解压,解压后可看到上面所说的xml文件信息。

详细原因分析

我们之前讨论过flushRows()和dimension的关系,现在更深入探讨一下。在Apache POI中,SXSSFSheet的flushRows()方法(无参数)会将内存中所有行刷新到磁盘临时文件,并清空内存中的行缓存。这会导致一个问题:当所有行都被刷新到磁盘后,内存中就没有行数据了,此时POI在调用write方法将数据写出到磁盘时计算dimension时可能会出错。

具体原因如下

sheet的dimension(即<dimension ref="A1:D10"/>)表示该Sheet的数据区域。POI在计算dimension时,通常依赖于sheet中缓存的行的信息(如第一个有数据的行和最后一个有数据的行)。对于SXSSFSheet,当行在内存中时,POI可以直接访问这些行来计算dimension;当行被刷新到磁盘后,POI需要依赖其他机制来记录或计算dimension。


问题发生时机


如果在写入数据后调用了SXSSFSheet.flushRows()(无参数)将所有行手动刷新到磁盘,那么内存中就没有行数据了。此时,如果POI在调用write方法生成最终的sheet.xml文件到磁盘,会因为缓存中没有该sheet页内容,导致dimension计算错误。
常见错误表现:
dimension被错误地设置为一个很小的范围(比如只包含一行或一列)。
或者dimension属性在sheet.xml中完全缺失。

解决方案


方法1) 避免在写入所有行后立即调用无参flushRows():除非必要,否则让POI自动管理刷新。在关闭工作簿之前,不需要手动刷新所有行,因为关闭工作簿时会自动处理。

方法2)在调用flushRows()之前确保至少有一行留在内存中:使用带参数的flushRows(remaining)并确保remaining>0,这样至少有一行留在内存中,POI可以基于这一行和已刷新的行信息正确计算dimension。

方法3)使用randomWindowSize参数,将其设置成非零值,让POI自动刷新xlsx数据到磁盘。

参考信息:

randomWindowSize参数含义

  • 正值:表示内存中将保留最近写入的行数。例如,如果设置为100,则内存中会保留最近写入的100行数据,当超过这个数量时,旧的行会被刷新到磁盘上。
  • 0:表示不使用窗口大小限制,所有行都将保留在内存中,这与普通的 XSSFWorkbook 行为类似,但可能会导致内存溢出问题。
  • -1:表示完全禁用窗口大小限制,并且不会自动刷新行到磁盘。这意味着所有的行都会保留在内存中,直到显式调用 flushRows() 方法或关闭工作簿。这种设置适用于需要对所有行进行随机访问的情况,但需要注意内存消耗。

remainning参数含义

  • remaining:指定刷新后保留在内存中的最近行数

  • 作用:手动触发刷新时,系统会:

    1. 将所有早于最近 remaining 行的数据写入磁盘临时文件

    2. 从内存中移除这些已刷新的行

    3. 仅保留最新的 remaining 行在内存中

// 示例:保留最近50行在内存
sheet.flushRows(50); // 内存行变化示意图
Before: [行0, 行1, ..., 行99]  // 内存中有100行
After:  [行50, 行51, ..., 行99] // 仅保留最后50行

 POI测试代码

下面的代码可以复现dimension写入错误问题。

要想写出正确的dimension参数,可以修改第35行代码为

xssfSheet.flushRows(1); // 保留1行数据在内存中
package org.example;import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.ss.usermodel.Row;import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.List;public class ExcelWriter {protected final SXSSFWorkbook xssfWorkbook;SXSSFSheet xssfSheet;public ExcelWriter(String sheetName) {// 禁用自动刷新xssfWorkbook = new SXSSFWorkbook(-1);// 创建一个新的工作表xssfSheet = xssfWorkbook.createSheet(sheetName);}public void writeData(List<List<String>> data) throws Exception {int rowNum = 0;for (List<String> rowData : data) {// 创建一行Row row = xssfSheet.createRow(rowNum++);int colNum = 0;for (String cellData : rowData) {// 在当前行中创建单元格并设置值row.createCell(colNum++).setCellValue(cellData);}// 如果需要,你可以在这里调用flushRows方法来手动刷新行到磁盘if (rowNum % 5 == 0) { // 每写入5行就刷新一次xssfSheet.flushRows(); // 不保留数据在内存中}}}public void saveToFile(String filePath) throws Exception {// 将工作簿写入文件try (FileOutputStream fileOut = new FileOutputStream(filePath)) {xssfWorkbook.write(fileOut);} finally {// 清理临时文件xssfWorkbook.dispose();}}public static void main(String[] args) throws Exception {ExcelWriter writer = new ExcelWriter("Sample Sheet");List<List<String>> data = Arrays.asList(Arrays.asList("Header1", "Header2", "Header3"),Arrays.asList("Row1-Col1", "Row1-Col2", "Row1-Col3"),Arrays.asList("Row2-Col1", "Row2-Col2", "Row2-Col3"),Arrays.asList("Row3-Col1", "Row3-Col2", "Row3-Col3"),Arrays.asList("Row4-Col1", "Row4-Col2", "Row4-Col3"));writer.writeData(data);writer.saveToFile("流式读取问题测试.xlsx");}
}

运行上面的代码需要添加maven依赖项如下

<dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>5.2.4</version>
</dependency>
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.2.4</version>
</dependency>

http://www.dtcms.com/a/286712.html

相关文章:

  • 配置本地git到gitlab并推送
  • 【机器学习】AdamW可调参数介绍及使用说明
  • 【LINUX操作系统】ssh远程连接---客户端Windows连接服务端虚拟机
  • 应用集成体系深度解析:从数据互通到流程协同
  • 你需要了解的 AI 智能体设计模式
  • compose multiplatform 常用库
  • Python FastMCP:让你的AI工具链飞起来
  • 深入解析操作系统中的文件控制块(FCB):从原理到现代实现演进
  • 利用动画实现热点图转圈循环放大效果
  • 深入理解 slab cache 内存分配全链路实现
  • 445、两数相加 II
  • 数字人直播:开启直播行业新纪元​
  • 基于LiteNetLib的Server/Client Demo
  • Android各版本适配方案总结归纳
  • 企业网站建设全攻略
  • Linux系统之:进程概念
  • JavaSE -- 对象序列化和反序列化详细讲解
  • HarmonyOS-ArkUI Web控件基础铺垫4--TCP协议- 断联-四次挥手解析
  • 全国计算机等级考试二级题库【C语言】:程序修改题型——结构体、可变数组、链表 自制答案详解合辑
  • 深度学习入门-深度学习简介
  • 屏显智能电子锁语音芯片方案新选择
  • Hinge Loss(铰链损失函数)详解:SVM 中的关键损失函数
  • C++实现单层时间轮
  • UE5网络联机函数
  • RSTP技术
  • Thread,ThreadLocal,ThreadLocalMap 三者的关系, 以及在实际开发中的应用【AI记录用】
  • mysql复制延迟如何处理
  • js中的微任务和宏任务的理解
  • python 实现将文件的非零数据扇区进行提取,并以偏移地址进行命名保存
  • AI(day10)模块化编程概念(模块、包、导入)及常见系统模块总结和第三方模块管理