Java使用FileOutputStream寫(xiě)Excel文件不落盤(pán)的解決方法
引言
最近在寫(xiě) Java 代碼處理 Excel 文件的時(shí)候,遇到了一個(gè)挺頭疼的問(wèn)題:使用 Apache POI 的 XSSFWorkbook.write(FileOutputStream) 方法寫(xiě)文件,代碼執(zhí)行得好好的,也沒(méi)有拋出異常,但生成的 Excel 文件卻打不開(kāi),甚至有時(shí)候文件大小還是 0 字節(jié),一點(diǎn)數(shù)據(jù)都沒(méi)有。
本來(lái)以為是 POI 的問(wèn)題,結(jié)果查了一圈文檔才發(fā)現(xiàn)——鍋還真不在 POI,而是我自己對(duì)文件輸出流的使用方式不太對(duì),尤其是涉及到 FileOutputStream 的時(shí)候,有些隱藏的“坑”沒(méi)注意到。
這篇文章就把我踩坑的過(guò)程整理一下,順便聊聊 Java 中如何正確地使用輸出流寫(xiě) Excel 文件,避免“寫(xiě)了但沒(méi)落盤(pán)”的問(wèn)題。
1. FileOutputStream 本身是沒(méi)有緩沖的
我們先來(lái)看看一個(gè)最常見(jiàn)的代碼片段:
Workbook workbook = new XSSFWorkbook(inputStream);
workbook.write(new FileOutputStream("output.xlsx"));這樣寫(xiě)看起來(lái)挺順,但你知道嗎?這里的 FileOutputStream 是直接把數(shù)據(jù)寫(xiě)到操作系統(tǒng)的,沒(méi)有中間的緩沖區(qū)。如果你的數(shù)據(jù)量很大,比如幾百 KB,甚至幾 MB,雖然代碼沒(méi)報(bào)錯(cuò),但你可能會(huì)發(fā)現(xiàn)文件根本沒(méi)寫(xiě)完整,或者干脆就是個(gè)空殼文件。
為什么?
因?yàn)椴僮飨到y(tǒng)本身還會(huì)有一個(gè)寫(xiě)入緩沖區(qū)(Page Cache),你并不能保證調(diào)用了 write() 之后,數(shù)據(jù)就馬上穩(wěn)穩(wěn)當(dāng)當(dāng)?shù)芈涞搅舜疟P(pán)上。如果你沒(méi)有關(guān)閉輸出流或者手動(dòng)調(diào)用 flush(),這些數(shù)據(jù)可能就一直在內(nèi)存里排隊(duì),根本沒(méi)真正寫(xiě)進(jìn)文件。
2. 沒(méi)有 flush() 或 close(),數(shù)據(jù)可能永遠(yuǎn)不會(huì)寫(xiě)進(jìn)硬盤(pán)
這是很多人常犯的一個(gè)錯(cuò)誤。看上去代碼沒(méi)問(wèn)題,但一旦你漏掉了 flush() 或者 close(),就會(huì)導(dǎo)致寫(xiě)入的數(shù)據(jù)停留在緩沖區(qū)里,始終不落盤(pán)。
比如下面這段代碼就是“反面教材”:
FileOutputStream fos = new FileOutputStream("output.xlsx");
workbook.write(fos);
// 沒(méi)有 fos.flush()
// 沒(méi)有 fos.close()你以為 write() 就完事了,其實(shí)根本沒(méi)有。解決方案很簡(jiǎn)單,要么在寫(xiě)完之后手動(dòng)調(diào)用:
fos.flush(); fos.close();
要么——更推薦的方式是使用 try-with-resources 來(lái)自動(dòng)幫你處理這些關(guān)閉操作。
3. 用 BufferedOutputStream 包裝一下,寫(xiě)得更穩(wěn)也更快
前面說(shuō)了,F(xiàn)ileOutputStream 是沒(méi)有緩沖的,這意味著它每調(diào)用一次 write() 就是一次底層系統(tǒng)調(diào)用,效率其實(shí)挺低的,尤其是在 Apache POI 這種寫(xiě) Excel 文件會(huì)反復(fù)調(diào)用 write() 的場(chǎng)景下。
所以非常推薦你用 BufferedOutputStream 包一下:
OutputStream bos = new BufferedOutputStream(new FileOutputStream("output.xlsx"));
workbook.write(bos);
bos.flush();
bos.close();多一層緩沖不僅能提升寫(xiě)入速度,更重要的是減少系統(tǒng)調(diào)用的頻率,能讓寫(xiě)入過(guò)程更加穩(wěn)定可靠。
4. 推薦用法:try-with-resources,優(yōu)雅又安全
說(shuō)了這么多,其實(shí)最靠譜、最簡(jiǎn)單、最不容易出錯(cuò)的寫(xiě)法,還是 Java 7 引入的 try-with-resources。
你只要這么寫(xiě):
try (
InputStream inputStream = new FileInputStream("template.xlsx");
Workbook workbook = new XSSFWorkbook(inputStream);
OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("output.xlsx"))
) {
workbook.write(outputStream);
}Java 會(huì)自動(dòng)幫你在塊結(jié)束后關(guān)閉 inputStream、workbook 和 outputStream,再也不用擔(dān)心忘了 flush() 或 close() 了,簡(jiǎn)直不要太爽。
5. 如果你就是不想用 try-with-resources,也請(qǐng)手動(dòng)關(guān)閉資源
當(dāng)然,也不是所有項(xiàng)目都能用上 Java 7 及以上版本的語(yǔ)法,博主前些時(shí)間就接到了一個(gè)Java 6的項(xiàng)目咨詢,還真不是,你發(fā)任你發(fā),我用Java 8。哈哈,有些老項(xiàng)目沒(méi)法用 try-with-resources。那也不是不能寫(xiě),你只要自己負(fù)責(zé)把所有資源都在 finally 中手動(dòng)關(guān)閉,也一樣可以穩(wěn)穩(wěn)落盤(pán)。
注意關(guān)閉的順序要搞對(duì),先關(guān) workbook,再關(guān)輸出流。
示例如下:
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 會(huì)自動(dòng) flush
} catch (IOException e) {
e.printStackTrace();
}
}記?。篶lose() 會(huì)自動(dòng)調(diào)用 flush(),但你也可以顯式加一遍 flush(),確保保險(xiǎn)。
大 Excel 文件時(shí)內(nèi)存溢出風(fēng)險(xiǎn)
- XSSFWorkbook 加載整個(gè) .xlsx 到內(nèi)存;
- 寫(xiě)入也可能消耗大量?jī)?nèi)存;
- 超過(guò)幾十萬(wàn)行時(shí)可能拋出 OOM。
大數(shù)據(jù)量推薦使用 SXSSFWorkbook:
SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook((XSSFWorkbook) workbook); sxssfWorkbook.write(outputStream); sxssfWorkbook.dispose(); // 清理臨時(shí)文件
6. 工作簿數(shù)據(jù)本身也別忘了檢查
最后還有一個(gè)冷門(mén)但真實(shí)的情況是——你其實(shí)根本就沒(méi)有往 Workbook 里寫(xiě)任何東西。這樣寫(xiě)出來(lái)的 Excel 文件雖然也是合法的 .xlsx,但打開(kāi)后是空白頁(yè),或者打開(kāi)報(bào)錯(cuò),看上去像是“沒(méi)寫(xiě)進(jìn)去”,其實(shí)是你沒(méi)寫(xiě)進(jìn)去數(shù)據(jù)……
你可以加個(gè)調(diào)試代碼確認(rèn):
log.info("sheet count: {}", workbook.getNumberOfSheets());7. 防止“寫(xiě)了但沒(méi)落盤(pán)”的幾點(diǎn) checklist
| 檢查項(xiàng) | 建議 |
|---|---|
| 使用緩沖流 | BufferedOutputStream 性能更穩(wěn) |
| 手動(dòng)或自動(dòng)關(guān)閉 | flush() + close 必不可少 |
| 優(yōu)先使用 try-with-resources | 推薦寫(xiě)法,防忘關(guān) |
| 大文件用 SXSSFWorkbook | 防止內(nèi)存溢出 |
| 確認(rèn)實(shí)際寫(xiě)入數(shù)據(jù) | 不要生成空文件 |
以上就是Java使用FileOutputStream寫(xiě)Excel文件不落盤(pán)的解決方法的詳細(xì)內(nèi)容,更多關(guān)于Java FileOutputStream寫(xiě)Excel文件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot+Mybatis使用Mapper接口注冊(cè)的幾種方式
本篇博文中主要介紹是Mapper接口與對(duì)應(yīng)的xml文件如何關(guān)聯(lián)的幾種姿勢(shì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
Java類變量和成員變量初始化過(guò)程的應(yīng)用介紹
昨天看了一本叫做《突破程序員基本功的16課》的書(shū),個(gè)人感覺(jué)還可以,主要對(duì)Java的技巧進(jìn)行了一些深入的講解,讓我對(duì)類的初始化和對(duì)象的創(chuàng)建有了新的認(rèn)識(shí)2013-04-04
mybatis-plus分頁(yè)查詢的實(shí)現(xiàn)實(shí)例
頁(yè)查詢是一項(xiàng)常用的數(shù)據(jù)庫(kù)查詢方法,本文主要介紹了mybatis-plus分頁(yè)查詢的實(shí)現(xiàn)實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-06-06
java 計(jì)算中位數(shù)的實(shí)現(xiàn)方法
這篇文章主要介紹了java 計(jì)算中位數(shù)的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Java8 stream 中利用 groupingBy 進(jìn)行多字段分組求和案例
這篇文章主要介紹了Java8 stream 中利用 groupingBy 進(jìn)行多字段分組求和案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08
Spring AOP切點(diǎn)表達(dá)式使用及說(shuō)明
這篇文章主要介紹了Spring AOP切點(diǎn)表達(dá)式使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05

