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. 运维保障:建立临时文件清理和监控机制

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

相关推荐
凸头4 小时前
以AtomicInteger为例的Atomic 类的底层CAS细节理解
java·jvm·算法
艾派森4 小时前
基于 Rokid CXR-M SDK 构建 AR 远程专家协作系统:从零实现眼镜端自定义 UI 与实时交互
java
cxyxiaokui0014 小时前
🔥不止于三级缓存:Spring循环依赖的全面解决方案
java·后端·spring
UCoding4 小时前
我们来学AI编程 -- vscode开发java
java·vscode·ai编程
一线大码4 小时前
开发 Java 项目时的命名规范
java·spring boot·后端
neoooo4 小时前
Apollo兜底口诀
java·后端·架构
程序员小假4 小时前
什么是线程池?它的工作原理?
java·后端
盖世英雄酱581364 小时前
java 深度调试【第一章:堆栈分析】
java·后端
本就一无所有 何惧重新开始5 小时前
Redis技术应用
java·数据库·spring boot·redis·后端·缓存