Java使用FileOutputStream寫Excel文件不落盤的解決方法
引言
最近在寫 Java 代碼處理 Excel 文件的時候,遇到了一個挺頭疼的問題:使用 Apache POI 的 XSSFWorkbook.write(FileOutputStream) 方法寫文件,代碼執(zhí)行得好好的,也沒有拋出異常,但生成的 Excel 文件卻打不開,甚至有時候文件大小還是 0 字節(jié),一點數(shù)據(jù)都沒有。
本來以為是 POI 的問題,結果查了一圈文檔才發(fā)現(xiàn)——鍋還真不在 POI,而是我自己對文件輸出流的使用方式不太對,尤其是涉及到 FileOutputStream 的時候,有些隱藏的“坑”沒注意到。
這篇文章就把我踩坑的過程整理一下,順便聊聊 Java 中如何正確地使用輸出流寫 Excel 文件,避免“寫了但沒落盤”的問題。
1. FileOutputStream 本身是沒有緩沖的
我們先來看看一個最常見的代碼片段:
Workbook workbook = new XSSFWorkbook(inputStream);
workbook.write(new FileOutputStream("output.xlsx"));這樣寫看起來挺順,但你知道嗎?這里的 FileOutputStream 是直接把數(shù)據(jù)寫到操作系統(tǒng)的,沒有中間的緩沖區(qū)。如果你的數(shù)據(jù)量很大,比如幾百 KB,甚至幾 MB,雖然代碼沒報錯,但你可能會發(fā)現(xiàn)文件根本沒寫完整,或者干脆就是個空殼文件。
為什么?
因為操作系統(tǒng)本身還會有一個寫入緩沖區(qū)(Page Cache),你并不能保證調(diào)用了 write() 之后,數(shù)據(jù)就馬上穩(wěn)穩(wěn)當當?shù)芈涞搅舜疟P上。如果你沒有關閉輸出流或者手動調(diào)用 flush(),這些數(shù)據(jù)可能就一直在內(nèi)存里排隊,根本沒真正寫進文件。
2. 沒有 flush() 或 close(),數(shù)據(jù)可能永遠不會寫進硬盤
這是很多人常犯的一個錯誤。看上去代碼沒問題,但一旦你漏掉了 flush() 或者 close(),就會導致寫入的數(shù)據(jù)停留在緩沖區(qū)里,始終不落盤。
比如下面這段代碼就是“反面教材”:
FileOutputStream fos = new FileOutputStream("output.xlsx");
workbook.write(fos);
// 沒有 fos.flush()
// 沒有 fos.close()你以為 write() 就完事了,其實根本沒有。解決方案很簡單,要么在寫完之后手動調(diào)用:
fos.flush(); fos.close();
要么——更推薦的方式是使用 try-with-resources 來自動幫你處理這些關閉操作。
3. 用 BufferedOutputStream 包裝一下,寫得更穩(wěn)也更快
前面說了,F(xiàn)ileOutputStream 是沒有緩沖的,這意味著它每調(diào)用一次 write() 就是一次底層系統(tǒng)調(diào)用,效率其實挺低的,尤其是在 Apache POI 這種寫 Excel 文件會反復調(diào)用 write() 的場景下。
所以非常推薦你用 BufferedOutputStream 包一下:
OutputStream bos = new BufferedOutputStream(new FileOutputStream("output.xlsx"));
workbook.write(bos);
bos.flush();
bos.close();多一層緩沖不僅能提升寫入速度,更重要的是減少系統(tǒng)調(diào)用的頻率,能讓寫入過程更加穩(wěn)定可靠。
4. 推薦用法:try-with-resources,優(yōu)雅又安全
說了這么多,其實最靠譜、最簡單、最不容易出錯的寫法,還是 Java 7 引入的 try-with-resources。
你只要這么寫:
try (
InputStream inputStream = new FileInputStream("template.xlsx");
Workbook workbook = new XSSFWorkbook(inputStream);
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("output.xlsx"))
) {
workbook.write(outputStream);
}Java 會自動幫你在塊結束后關閉 inputStream、workbook 和 outputStream,再也不用擔心忘了 flush() 或 close() 了,簡直不要太爽。
5. 如果你就是不想用 try-with-resources,也請手動關閉資源
當然,也不是所有項目都能用上 Java 7 及以上版本的語法,博主前些時間就接到了一個Java 6的項目咨詢,還真不是,你發(fā)任你發(fā),我用Java 8。哈哈,有些老項目沒法用 try-with-resources。那也不是不能寫,你只要自己負責把所有資源都在 finally 中手動關閉,也一樣可以穩(wěn)穩(wěn)落盤。
注意關閉的順序要搞對,先關 workbook,再關輸出流。
示例如下:
Workbook workbook = null;
BufferedOutputStream bos = null;
try {
workbook = new XSSFWorkbook();
bos = new BufferedOutputStream(new FileOutputStream("output.xlsx"));
workbook.write(bos);
bos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (workbook != null) workbook.close();
if (bos != null) bos.close(); // close 會自動 flush
} catch (IOException e) {
e.printStackTrace();
}
}記?。篶lose() 會自動調(diào)用 flush(),但你也可以顯式加一遍 flush(),確保保險。
大 Excel 文件時內(nèi)存溢出風險
- XSSFWorkbook 加載整個 .xlsx 到內(nèi)存;
- 寫入也可能消耗大量內(nèi)存;
- 超過幾十萬行時可能拋出 OOM。
大數(shù)據(jù)量推薦使用 SXSSFWorkbook:
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook((XSSFWorkbook) workbook); sxssfWorkbook.write(outputStream); sxssfWorkbook.dispose(); // 清理臨時文件
6. 工作簿數(shù)據(jù)本身也別忘了檢查
最后還有一個冷門但真實的情況是——你其實根本就沒有往 Workbook 里寫任何東西。這樣寫出來的 Excel 文件雖然也是合法的 .xlsx,但打開后是空白頁,或者打開報錯,看上去像是“沒寫進去”,其實是你沒寫進去數(shù)據(jù)……
你可以加個調(diào)試代碼確認:
log.info("sheet count: {}", workbook.getNumberOfSheets());7. 防止“寫了但沒落盤”的幾點 checklist
| 檢查項 | 建議 |
|---|---|
| 使用緩沖流 | BufferedOutputStream 性能更穩(wěn) |
| 手動或自動關閉 | flush() + close 必不可少 |
| 優(yōu)先使用 try-with-resources | 推薦寫法,防忘關 |
| 大文件用 SXSSFWorkbook | 防止內(nèi)存溢出 |
| 確認實際寫入數(shù)據(jù) | 不要生成空文件 |
以上就是Java使用FileOutputStream寫Excel文件不落盤的解決方法的詳細內(nèi)容,更多關于Java FileOutputStream寫Excel文件的資料請關注腳本之家其它相關文章!
相關文章
springboot整合netty-mqtt-client實現(xiàn)Mqtt消息的訂閱和發(fā)布示例
本文主要介紹了springboot整合netty-mqtt-client實現(xiàn)Mqtt消息的訂閱和發(fā)布示例,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
Spring?Boot?Admin集成與自定義監(jiān)控告警示例詳解
SpringBootAdmin是一個管理和監(jiān)控SpringBoot應用程序的工具,可通過集成和配置實現(xiàn)應用監(jiān)控與告警功能,本文給大家介紹Spring?Boot?Admin集成與自定義監(jiān)控告警示例詳解,感興趣的朋友跟隨小編一起看看吧2024-09-09

