关于Java对excel 写入操作导致文件破损问题

写入excel操作

由于公司经常要用excel统计工数,并且手动录入到一个”项目总工数“的excel

所以我就用Java写了一个程序来提升效率,不再手动录入,直接小手一点,让代码自己录入。

但是发现一个问题,当我录入的时候,读取和针对数据处理没有问题,但是一旦写入就会报错

出现了报错

org.apache.poi.EmptyFileException: The supplied file was empty (zero bytes long)

说我写入了一个空文件

实际上是有数据的,在写入的过程中变成了空文件(0kb)

后来在调试的过程中发现是

// 写入的时候变成了0kb
FileOutputStream fos = new FileOutputStream(targetExcelFilePath)

原因

常见原因

这里写一下常见的几个原因

  • 文件被其他进程占用:如果文件正在被另一个程序(如Excel应用程序)使用,可能会导致写入操作失败。

  • 文件权限问题:程序可能没有足够的权限来读取和写入文件。

  • 文件输出流未正确关闭:如果文件输出流未正确关闭,可能会导致文件损坏。

  • 异常未被捕获:如果在写入过程中发生异常但未被捕获和处理,可能会导致文件状态异常。

  • 并发问题:如果有多个线程同时访问和修改文件,可能会导致文件损坏。

我的原因

实际上我就是因为文件输入输出流没有正常关闭所导致的

使用 try-with-resources 确保流自动关闭
明确关闭顺序:先关输入流,再处理输出流

Java代码如下

      //保证输入流是在try catch内部即可
        try (FileInputStream Fis = new FileInputStream(ExcelFilePath);
             Workbook Workbook = new XSSFWorkbook(Fis);)  {
			//针对excel 的操作
			......(数了好几次确实是六个点,严谨(*^_^*))
			......
			......
			//最后写入输出流的时候也要保证try catch内部
 			try (FileOutputStream fos = new FileOutputStream(ExcelFilePath)) {
                Workbook.write(fos);
            }
}          

原因分析

在 try 内部和外部使用的区别,核心问题在于资源管理和异常处理。

当直接使用

FileOutputStream fos = new FileOutputStream(filePath)

而不使用 try-with-resources 或 finally 块时,问题在于:

  • 文件句柄未正确关闭

    • 操作系统会锁定文件直到流关闭

    • 未关闭的流会导致文件处于"未完成写入"状态

  • 异常中断风险

    • 如果写入过程中发生异常(如内存不足、IO错误)

    • 流没有机会执行关闭操作

    • 文件会停留在"部分写入"状态(0KB)

  • 缓冲区的刷新问题

// 错误示例:
FileOutputStream fos = new FileOutputStream(filePath);
workbook.write(fos); // 如果这里出错
// 没有 fos.close() -> 缓冲区未刷新,文件损坏

为什么 try-with-resources 能解决

// 正确示例:
try (FileOutputStream fos = new FileOutputStream(filePath)) {
    workbook.write(fos);
}

Java 的 try-with-resources 语法实际上等价于:

FileOutputStream fos = null;
try {
    fos = new FileOutputStream(filePath);
    workbook.write(fos);
} finally {
    if (fos != null) {
        try {
            fos.close(); // 关键保证!
        } catch (IOException e) {
            // 关闭异常处理
        }
    }
}

核心保护机制

  • 自动关闭保证

    • 无论是否发生异常

    • finally 块都会执行 close() 操作

    • 确保操作系统文件句柄释放

  • 缓冲区强制刷新

public void close() throws IOException {
    flush(); // 首先刷新缓冲区
    close0(); // 然后关闭底层资源
}
  • 异常传播安全
    • 如果 write() 出错,close() 仍然会执行

    • 如果 close() 出错,会添加 suppressed 异常

对比试验

假设有以下两种写法:
危险写法(文件可能变0KB):

public void unsafeWrite() throws Exception {
    Workbook workbook = ... // 已加载的工作簿
    FileOutputStream fos = new FileOutputStream("test.xlsx");
    workbook.write(fos); // 如果这里抛出异常
    // 没有 close() 调用!
}

安全写法(使用 try-with-resources):

public void safeWrite() throws Exception {
    Workbook workbook = ... // 已加载的工作簿
    try (FileOutputStream fos = new FileOutputStream("test.xlsx")) {
        workbook.write(fos);
    }
}

当发生异常时的区别

场景危险写法结果安全写法结果
写入过程正常文件完整(但依赖JVM清理)文件完整
磁盘空间不足文件0KB(流未关闭)文件0KB但资源已释放
权限错误文件0KB+资源泄漏抛出异常但无文件损坏

最佳实践建议

  1. 始终使用try-with-resources
// 双资源安全模式
try (InputStream is = new FileInputStream(file);
     Workbook workbook = WorkbookFactory.create(is);
     OutputStream os = new FileOutputStream(file)) {
    // 操作workbook
    workbook.write(os);
}
  1. 对于关键业务数据,使用临时文件策略:
Path tempFile = Files.createTempFile("excel-", ".tmp");
try (OutputStream os = Files.newOutputStream(tempFile)) {
    workbook.write(os);
}
// 原子替换
Files.move(tempFile, Path.of(originalPath), REPLACE_EXISTING);

  1. 额外保护,手动刷新:
try (FileOutputStream fos = ...) {
    workbook.write(fos);
    fos.flush(); // 显式刷新
    fos.getFD().sync(); // 强制写入磁盘(可选)
}

总结关键点

  1. try-with-resources 保证流被关闭(刷新缓冲区+释放资源)
  2. 直接覆盖原文件本质危险,临时文件策略是最佳实践
  3. POI 操作应始终配合 try-with-resources 使用

希望大家点个赞,谢谢大家

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值