Apache POI SXSSFWorkbook 报错“没有那个文件或目录”问题排查与解决方案

关键词:Java, Apache POI, SXSSFWorkbook, 临时目录, tmpfs, java.io.tmpdir, No such file or directory

在使用 Apache POI 处理大型 Excel 文件时,SXSSFWorkbook 是一个常用的流式写入工具。然而,许多开发者会遇到一个看似简单却令人困惑的错误:

复制代码
java.lang.RuntimeException: java.io.IOException: 没有那个文件或目录
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createAndRegisterSXSSFSheet(SXSSFWorkbook.java:640)
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheet(SXSSFWorkbook.java:629)
	at org.apache.poi.xssf.streaming.SXSSFWorkbook.createSheet(SXSSFWorkbook.java:71)

本文将详细记录这个问题的排查过程、根本原因分析,并提供完整的解决方案和最佳实践建议。

问题背景

SXSSFWorkbook 是 Apache POI 提供的用于处理大型 Excel 文件的流式工作簿类。它通过将数据分批写入临时文件来减少内存占用,非常适合处理包含数万甚至数十万行数据的 Excel 文件。

然而,当系统临时目录配置不当时,就会出现上述错误。

问题排查过程

第一步:确认基础权限

首先检查系统临时目录的基本情况:

bash 复制代码
# 查看java临时目录
jinfo -sysprops <pid> | grep java.io.tmpdir
# 输出:java.io.tmpdir = /tmp

# 查看临时目录权限
ls -lha /tmp
# 输出:drwxrwxrwt 5 root root 120 10月 14 10:45 .

# 查看 Java 进程用户
ps aux | grep java
# 输出:root 用户运行

# 测试手动创建文件
sudo -u root touch /tmp/test-file-$(date +%s).tmp
# 成功创建,说明基础权限正常

结论:目录权限和用户权限都没有问题。

第二步:检查磁盘空间和挂载信息

bash 复制代码
# 查看磁盘使用情况
df -h /tmp
# 输出:
# 文件系统        容量  已用  可用 已用% 挂载点
# tmpfs           936M   32K  936M    1% /tmp

# 查看挂载选项
mount | grep /tmp
# 输出:tmpfs on /tmp type tmpfs (rw,nosuid,nodev)

关键发现/tmp 目录使用的是 tmpfs(内存文件系统),总容量仅 936MB。

第三步:分析根本原因

虽然手动测试可以创建小文件,但 SXSSFWorkbook 在处理大型 Excel 时会:

  1. 生成多个临时 XML 文件(如 poi-sxssf-sheet-*.xml
  2. 每个临时文件可能达到几十 MB 甚至更大
  3. 当临时文件总大小超过 tmpfs 的 936MB 限制时,系统无法创建新文件
  4. 错误信息被误报为"没有那个文件或目录",实际是空间不足

解决方案

最佳解决方案:使用磁盘临时目录

  1. 创建专用临时目录

    bash 复制代码
    mkdir -p /app/chp-fbs/tmp
    chmod 755 /app/chp-fbs/tmp
  2. 启动 Java 应用时指定临时目录

    bash 复制代码
    java -Djava.io.tmpdir=/app/chp-fbs/tmp -jar your-application.jar
  3. 验证解决方案

    bash 复制代码
    # 启动应用后检查临时文件创建
    ls -la /app/chp-fbs/tmp/

为什么这个方案有效?

  • 利用充足的磁盘空间:从 936MB 内存扩展到 33GB 磁盘空间
  • 避免内存压力:不再占用宝贵的系统内存
  • 提高系统稳定性:即使处理超大 Excel 文件也不会影响系统性能

最佳实践建议

1. 生产环境避免依赖系统 /tmp

问题 :系统 /tmp 可能是 tmpfs,容量有限
建议:为每个应用创建专用的临时目录

bash 复制代码
# 应用部署时创建临时目录
APP_HOME="/opt/myapp"
mkdir -p $APP_HOME/tmp

2. 启动脚本中显式指定临时目录

bash 复制代码
#!/bin/bash
# start.sh
APP_HOME="/opt/myapp"
JAVA_OPTS="-Djava.io.tmpdir=$APP_HOME/tmp"
JAVA_OPTS="$JAVA_OPTS -Xms1g -Xmx2g"
# ... 其他 JVM 参数

java $JAVA_OPTS -jar $APP_HOME/app.jar

3. 代码层面的临时目录控制

如果需要更精细的控制,可以在代码中指定:

java 复制代码
import java.io.File;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

public class ExcelService {
    public void createLargeExcel() {
        // 使用应用专用临时目录
        File appTempDir = new File("/opt/myapp/tmp");
        if (!appTempDir.exists()) {
            appTempDir.mkdirs();
        }
        
        // 创建 SXSSFWorkbook 时指定临时目录
        SXSSFWorkbook workbook = new SXSSFWorkbook(null, 100, false, appTempDir);
        
        try {
            // 业务逻辑...
        } finally {
            // 重要:清理临时文件
            workbook.dispose();
        }
    }
}

4. 临时文件清理策略

自动清理脚本

bash 复制代码
# 添加到 crontab,每天凌晨 2 点清理 1 天前的临时文件
0 2 * * * find /opt/myapp/tmp -name 'poi-sxssf*' -mtime +1 -delete

应用关闭时清理

java 复制代码
// 确保在应用关闭时调用
@PreDestroy
public void cleanup() {
    if (workbook != null) {
        workbook.dispose();
    }
}

5. 监控和告警

bash 复制代码
# 监控临时目录大小
du -sh /opt/myapp/tmp/

# 设置告警阈值(例如超过 10GB)
if [ $(du -s /opt/myapp/tmp/ | cut -f1) -gt 10485760 ]; then
    echo "临时目录过大,需要清理!" | mail -s "临时目录告警" admin@example.com
fi

6. 调整 SXSSFWorkbook 参数

根据内存情况优化窗口大小:

java 复制代码
// 减少内存中的行数,更早写入磁盘
SXSSFWorkbook workbook = new SXSSFWorkbook(50); // 默认是 100

// 启用临时文件压缩(减少磁盘空间使用)
SXSSFWorkbook workbook = new SXSSFWorkbook(null, 100, true, tempDir);

常见误区

误区 1:认为"权限正常就一定能创建文件"

  • 事实:除了权限,还需要考虑磁盘空间、inode 限制、文件系统类型等因素

误区 2:忽略 tmpfs 的内存限制

  • 事实:tmpfs 虽然快,但受限于内存大小,不适合存储大量临时数据

误区 3:依赖系统默认临时目录

  • 事实 :不同系统、不同环境的 /tmp 配置可能不同,应该显式指定

总结

SXSSFWorkbook 的"没有那个文件或目录"错误通常不是真正的路径问题,而是临时文件系统空间不足导致的误报。通过以下步骤可以有效避免和解决此类问题:

  1. 识别问题 :检查 /tmp 是否为 tmpfs 及其容量限制
  2. 解决方案:为应用创建专用磁盘临时目录
  3. 预防措施 :在启动脚本中显式指定 java.io.tmpdir
  4. 运维保障:建立临时文件清理和监控机制

这个案例也提醒我们,在生产环境中处理大文件时,要充分考虑系统资源限制,不要过度依赖默认配置。显式的资源配置和完善的监控机制是保证系统稳定性的关键。

相关推荐
李慕婉学姐10 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆11 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin12 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200512 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉12 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国12 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824812 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈13 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9913 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹13 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理