关于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+资源泄漏 | 抛出异常但无文件损坏 |
最佳实践建议
- 始终使用try-with-resources:
// 双资源安全模式
try (InputStream is = new FileInputStream(file);
Workbook workbook = WorkbookFactory.create(is);
OutputStream os = new FileOutputStream(file)) {
// 操作workbook
workbook.write(os);
}
- 对于关键业务数据,使用临时文件策略:
Path tempFile = Files.createTempFile("excel-", ".tmp");
try (OutputStream os = Files.newOutputStream(tempFile)) {
workbook.write(os);
}
// 原子替换
Files.move(tempFile, Path.of(originalPath), REPLACE_EXISTING);
- 额外保护,手动刷新:
try (FileOutputStream fos = ...) {
workbook.write(fos);
fos.flush(); // 显式刷新
fos.getFD().sync(); // 强制写入磁盘(可选)
}
总结关键点
- try-with-resources 保证流被关闭(刷新缓冲区+释放资源)
- 直接覆盖原文件本质危险,临时文件策略是最佳实践
- POI 操作应始终配合 try-with-resources 使用
希望大家点个赞,谢谢大家
1977

被折叠的 条评论
为什么被折叠?



