摘要:消費之后,多線程處理文件導(dǎo)出,生成文件后上傳到等文件服務(wù)器。前端直接查詢并且展現(xiàn)對應(yīng)的任務(wù)執(zhí)行列表,去等文件服務(wù)器下載文件即可。這客戶體驗不友好,而且網(wǎng)絡(luò)傳輸,系統(tǒng)占用多種問題。拓展閱讀導(dǎo)出最佳實踐框架
產(chǎn)品需求
產(chǎn)品經(jīng)理需要導(dǎo)出一個頁面的所有的信息到 EXCEL 文件。
需求分析對于 excel 導(dǎo)出,是一個很常見的需求。
最常見的解決方案就是使用 poi 直接同步導(dǎo)出一個 excel 文件。
客戶體驗 & 服務(wù)性能客戶體驗
如果導(dǎo)出的文件比較大,比如幾十萬條數(shù)據(jù),同步導(dǎo)出頁面就會卡主,用戶無法進行其他操作。
服務(wù)性能
導(dǎo)出的時候,任務(wù)比較耗時就會阻塞主線程。
如果導(dǎo)出的服務(wù)是暴露給外部(前后端分離),這種大量的數(shù)據(jù)傳輸十分消耗性能。
解決方案使用異常處理導(dǎo)出請求,后臺 MQ 通知自己進行處理。
MQ 消費之后,多線程處理 excel 文件導(dǎo)出,生成文件后上傳到 FTP 等文件服務(wù)器。
前端直接查詢并且展現(xiàn)對應(yīng)的任務(wù)執(zhí)行列表,去 FTP 等文件服務(wù)器下載文件即可。
EXCEL 導(dǎo)出需要考慮的問題 OOM正常的 poi 在處理比較大的 excel 的時候,會出現(xiàn)內(nèi)存溢出。
網(wǎng)上的解決方案也比較多。
比如官方的 SXSSF (Since POI 3.8 beta3) 解決方式。
或者使用封裝好的包
easypoi ExcelBatchExportServer
hutool BigExcelWriter
原理都是強制使用 xssf 版本的Excel。
你也可以使用 easyexcel,當然這個注釋文檔有些欠缺,而且設(shè)計的比較復(fù)雜,不是很推薦。
我這里使用的是 hutool BigExcelWriter,
懶得自己再寫一遍。
如果一次查詢 100W 條數(shù)據(jù)庫,然后把這些信息全部加載到內(nèi)存中,是不可取的。
建議有2個:
限制每一次分頁的數(shù)量。比如一次最多查詢 1w 條。分成 100 次查詢。(必須)
限制查詢得總條數(shù)。比如限制為最多 10W 條。(根據(jù)實際情況選擇)
雖然使用者提出要導(dǎo)出類似于 3 個月的所有信息,但是數(shù)量太多,毫無意義。(提出者自己可能體會不到)
盡量避免 FULL-GC 的情況發(fā)生,因為目前的所有方式對于 excel 的輸出流都會占用內(nèi)存,100W 條很容易導(dǎo)致 FULL-GC。
數(shù)據(jù)庫的壓力去數(shù)據(jù)庫讀取的時候一定要記得分頁,免得給數(shù)據(jù)庫太大的壓力。
一次讀取太多,也會導(dǎo)致內(nèi)存直線上升。
比如 100W 條數(shù)據(jù),則分成 100 次去數(shù)據(jù)庫讀取。
網(wǎng)絡(luò)傳輸傳統(tǒng)的 excel 導(dǎo)出,都是前端一個請求,直接 HTTP 同步返回。導(dǎo)出 100W 條,就在那里傻等。
這客戶體驗不友好,而且網(wǎng)絡(luò)傳輸,系統(tǒng)占用多種問題。
建議使用異步處理的方式,將文件上傳到文件服務(wù)器。前端直接去文件服務(wù)器讀取。
編程的便利性對于上面提到的工具,比如 Hutool,在表頭的處理方面沒法很方便的統(tǒng)一。
你可以自己定義類似于 easypoi/easyexcel 中的注解,自己反射解析。
然后統(tǒng)一處理表頭即可。
IExcel 方便優(yōu)雅的 excel 框架 特性OO 的方式操作 excel,編程更加方便優(yōu)雅。
sax 模式讀取,SXSS 模式寫入。避免 excel 大文件 OOM。
基于注解,編程更加靈活。
寫入可以基于對象列表,也可以基于 Map,實際使用更加方便。
設(shè)計簡單,注釋完整。方便大家學習改造。
后期特性讀取跳過空白行
excel 樣式相關(guān)的注解開發(fā)
創(chuàng)作緣由實際工作和學習中,apache poi 操作 excel 過于復(fù)雜。
近期也看了一些其他的工具框架:
easypoi
easyexcel
hutool-poi
都或多或少難以滿足自己的實際需要,于是就自己寫了一個操作 excel 導(dǎo)出的工具。
快速開始 引入 Jar使用 maven 管理。
定義對象com.github.houbb iexcel 0.0.2
你可以直接參考 ExcelUtilTest.java
定義一個需要寫入/讀取的 excel 對象。
ExcelFieldModel.java
只有聲明了 @ExcelField 的屬性才會被處理,使用說明:@ExcelField
public class ExcelFieldModel { @ExcelField private String name; @ExcelField(headName = "年齡") private String age; @ExcelField(mapKey = "EMAIL", writeRequire = false, readRequire = false) private String email; @ExcelField(mapKey = "ADDRESS", headName = "地址", writeRequire = true) private String address; //getter and setter }寫入例子 IExcelWriter 的實現(xiàn)
IExcelWriter 有幾個實現(xiàn)類,你可以直接 new 或者借助 ExcelUtil 類去創(chuàng)建。
IExcelWriter 實現(xiàn)類 | ExcelUtil 如何創(chuàng)建 | 說明 |
---|---|---|
HSSFExcelWriter | ExcelUtil.get03ExcelWriter() | 2003 版本的 excel |
XSSFExcelWriter | ExcelUtil.get07ExcelWriter() | 2007 版本的 excel |
SXSSFExcelWriter | ExcelUtil.getBigExcelWriter() | 大文件 excel,避免 OOM |
IExcelWriter 接口說明寫入到 2003
excelWriter03Test()
一個將對象列表寫入 2003 excel 文件的例子。
/** * 寫入到 03 excel 文件 */ @Test public void excelWriter03Test() { // 待生成的 excel 文件路徑 final String filePath = "excelWriter03.xls"; // 對象列表 Listmodels = buildModelList(); try(IExcelWriter excelWriter = ExcelUtil.get03ExcelWriter(); OutputStream outputStream = new FileOutputStream(filePath)) { // 可根據(jù)實際需要,多次寫入列表 excelWriter.write(models); // 將列表內(nèi)容真正的輸出到 excel 文件 excelWriter.flush(outputStream); } catch (IOException e) { throw new ExcelRuntimeException(e); } }
buildModelList()
/** * 構(gòu)建測試的對象列表 * @return 對象列表 */ private List一次性寫入到 2007 excelbuildModelList() { List models = new ArrayList<>(); ExcelFieldModel model = new ExcelFieldModel(); model.setName("測試1號"); model.setAge("25"); model.setEmail("123@gmail.com"); model.setAddress("貝克街23號"); ExcelFieldModel modelTwo = new ExcelFieldModel(); modelTwo.setName("測試2號"); modelTwo.setAge("30"); modelTwo.setEmail("125@gmail.com"); modelTwo.setAddress("貝克街26號"); models.add(model); models.add(modelTwo); return models; }
有時候列表只寫入一次很常見,所有就簡單的封裝了下:
/** * 只寫入一次列表 * 其實是對原來方法的簡單封裝 */ @Test public void onceWriterAndFlush07Test() { // 待生成的 excel 文件路徑 final String filePath = "onceWriterAndFlush07.xlsx"; // 對象列表 List讀取例子models = buildModelList(); // 對應(yīng)的 excel 寫入對象 IExcelWriter excelWriter = ExcelUtil.get07ExcelWriter(); // 只寫入一次列表 ExcelUtil.onceWriteAndFlush(excelWriter, models, filePath); }
excel 讀取時會根據(jù)文件名稱判斷是哪個版本的 excel。
IExcelReader 的實現(xiàn)IExcelReader 有幾個實現(xiàn)類,你可以直接 new 或者借助 ExcelUtil 類去創(chuàng)建。
IExcelReader 實現(xiàn)類 | ExcelUtil 如何創(chuàng)建 | 說明 |
---|---|---|
ExcelReader | ExcelUtil.getExcelReader() | 小文件的 excel 讀取實現(xiàn) |
Sax03ExcelReader | ExcelUtil.getBigExcelReader() | 大文件的 2003 excel 讀取實現(xiàn) |
Sax07ExcelReader | ExcelUtil.getBigExcelReader() | 大文件的 2007 excel 讀取實現(xiàn) |
IExcelReader 接口說明excel 讀取的例子
/** * 讀取測試 */ @Test public void readWriterTest() { File file = new File("excelWriter03.xls"); IExcelReaderExcelField 注解說明excelReader = ExcelUtil.getExcelReader(file); List models = excelReader.readAll(ExcelFieldModel.class); System.out.println(models); }
@ExcelField 的屬性說明如下:
屬性 | 類型 | 默認值 | 說明 |
---|---|---|---|
mapKey | String | "" | 僅用于生成的入?yún)?map 時,會將 map.key 對應(yīng)的值映射到 bean 上。如果不傳:默認使用當前字段名稱 |
headName | String | "" | excel 表頭字段名稱,如果不傳:默認使用當前字段名稱 |
writeRequire | boolean | true | excel 文件是否需要寫入此字段 |
readRequire | boolean | true | excel 文件是否讀取此字段 |
/** * 寫出數(shù)據(jù),本方法只是將數(shù)據(jù)寫入Workbook中的Sheet,并不寫出到文件指定 sheet
** data中元素支持的類型有: *
* 1. Bean,既元素為一個Bean,第一個Bean的字段名列表會作為首行,剩下的行為Bean的字段值列表,data表示多行* @param data 數(shù)據(jù) * @return this */ IExcelWriter write(Collection> data); /** * 寫出數(shù)據(jù),本方法只是將數(shù)據(jù)寫入Workbook中的Sheet,并不寫出到文件
*
* 將 map 按照 targetClass 轉(zhuǎn)換為對象列表 * 應(yīng)用場景: 直接 mybatis mapper 查詢出的 map 結(jié)果,或者其他的構(gòu)造結(jié)果。 * @param mapList map 集合 * @param targetClass 目標類型 * @return this */ IExcelWriter write(Collection
創(chuàng)建 IExcelWriter 的時候,可以指定 sheet 的下標或者名稱。來指定寫入的 sheet。
是否包含表頭創(chuàng)建 IExcelWriter 的后,可以調(diào)用 excelWriter.containsHead(bool) 指定是否生成 excel 表頭。
IExcelReader 接口說明/** * 讀取當前 sheet 的所有信息 * @param tClass 對應(yīng)的 javabean 類型 * @return 對象列表 */ List指定 sheetreadAll(Class tClass); /** * 讀取指定范圍內(nèi)的 * @param tClass 泛型 * @param startIndex 開始的行信息(從0開始) * @param endIndex 結(jié)束的行信息 * @return 讀取的對象列表 */ List read(Class tClass, final int startIndex, final int endIndex);
創(chuàng)建 IExcelReader 的時候,可以指定 sheet 的下標或者名稱。來指定讀取的 sheet。
注意:大文件 sax 讀取模式,只支持指定 sheet 的下標。
是否包含表頭創(chuàng)建 IExcelReader 的后,可以調(diào)用 excelReader.containsHead(bool) 指定是否讀取 excel 表頭。
拓展閱讀excel 導(dǎo)出最佳實踐
iexcel 框架
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/72442.html
摘要:并且在對的抽象中,每一行,每一個單元格都是一個對象。對支持使用官方例子需要繼承,覆蓋方法,每讀取到一個單元格的數(shù)據(jù)則會回調(diào)次方法。概要Java對Excel的操作一般都是用POI,但是數(shù)據(jù)量大的話可能會導(dǎo)致頻繁的FGC或OOM,這篇文章跟大家說下如果避免踩POI的坑,以及分別對于xls和xlsx文件怎么優(yōu)化大批量數(shù)據(jù)的導(dǎo)入和導(dǎo)出。一次線上問題這是一次線上的問題,因為一個大數(shù)據(jù)量的Excel導(dǎo)出...
摘要:我最近做的一個工具就是讀取計算機中的以及文件。經(jīng)常在讀取某些特別大的文件的時候都會帶來一個內(nèi)存溢出的問題。以上,就是我在使用讀取文件的一些探索和發(fā)現(xiàn),希望對你能有所幫助。 POI是 Apache 旗下一款讀寫微軟家文檔聲名顯赫的類庫。應(yīng)該很多人在做報表的導(dǎo)出,或者創(chuàng)建 word 文檔以及讀取之類的都是用過 POI。POI 也的確對于這些操作帶來很大的便利性。我最近做的一個工具就是讀取計...
摘要:大多數(shù)待遇豐厚的開發(fā)職位都要求開發(fā)者精通多線程技術(shù)并且有豐富的程序開發(fā)調(diào)試優(yōu)化經(jīng)驗,所以線程相關(guān)的問題在面試中經(jīng)常會被提到。將對象編碼為字節(jié)流稱之為序列化,反之將字節(jié)流重建成對象稱之為反序列化。 JVM 內(nèi)存溢出實例 - 實戰(zhàn) JVM(二) 介紹 JVM 內(nèi)存溢出產(chǎn)生情況分析 Java - 注解詳解 詳細介紹 Java 注解的使用,有利于學習編譯時注解 Java 程序員快速上手 Kot...
摘要:而常用的包需要把所有數(shù)據(jù)拿到后才能生成,在面對生成超大數(shù)據(jù)量的文件時這顯然是會造成內(nèi)存溢出的,所以考慮使用讓邊寫入輸出流邊讓瀏覽器下載的形式來完成需求。 最近接到一個需求,通過選擇的時間段導(dǎo)出對應(yīng)的用戶訪問日志到excel中, 由于用戶量較大,經(jīng)常會有導(dǎo)出50萬加數(shù)據(jù)的情況。而常用的PHPexcel包需要把所有數(shù)據(jù)拿到后才能生成excel, 在面對生成超大數(shù)據(jù)量的excel文件時這顯然...
閱讀 2542·2021-10-11 10:58
閱讀 1020·2019-08-29 13:58
閱讀 1661·2019-08-26 13:32
閱讀 830·2019-08-26 10:40
閱讀 3255·2019-08-26 10:18
閱讀 1755·2019-08-23 14:18
閱讀 1105·2019-08-23 10:54
閱讀 435·2019-08-22 18:39