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

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

相关推荐
元亓亓亓3 小时前
Leet热题100--208. 实现 Trie (前缀树)--中等
java·开发语言
拿破轮3 小时前
不小心在idea中点了add 到版本控制 怎么样恢复?
java·ide·intellij-idea
cynicme8 小时前
力扣3318——计算子数组的 x-sum I(偷懒版)
java·算法·leetcode
青云交9 小时前
Java 大视界 -- Java 大数据在智能教育学习效果评估与教学质量改进实战
java·实时分析·生成式 ai·个性化教学·智能教育·学习效果评估·教学质量改进
崎岖Qiu9 小时前
【设计模式笔记17】:单例模式1-模式分析
java·笔记·单例模式·设计模式
Lei活在当下10 小时前
【现代 Android APP 架构】09. 聊一聊依赖注入在 Android 开发中的应用
java·架构·android jetpack
不穿格子的程序员10 小时前
从零开始刷算法-栈-括号匹配
java·开发语言·
lkbhua莱克瓦2411 小时前
Java练习-正则表达式 1
java·笔记·正则表达式·github
yue00811 小时前
C#类继承
java·开发语言·c#
凯芸呢11 小时前
Java中的数组(续)
java·开发语言·数据结构·算法·青少年编程·排序算法·idea