常見的導出可能上百萬甚至千萬的數據量業務場景
- 歷史訂單的導出
- 歷史訂單明細的導出
- 歷史支付明細的導出
- 用戶信息的導出
- 等等等
遇到這些問題我想你一定頭疼過,客服或財務可能會在你睡覺的時候找上你
財務: “大王趕快起來看下系統,系統又卡住了”
大王:“你小子又幹啥了”
財務: “我剛導出了一個月的訂單明細”
大王:“那應該,沒啥問題呀,我們一個月撐死也就1w筆訂單”
財務: “上個月不是搞了個活動嗎,現在有100w筆訂單,然後就導不出來了”
大王默默心裏吐槽,但還是從牀上爬起來,打開日誌一看,果然是這個問題:“內存溢出”
- 因爲往常訂單少,根據時間查詢訂單沒有分頁,大量數據查詢超時
- 之前使用XSSFWorkbook下載的時候大量數據轉換的輸出流內存溢出
解決方案
- 對大量數據進行分頁查詢
- 導出方法使用SXSSFWorkbook解決
分頁就不說了,自己實現
那如何最簡單的使用SXSSFWorkbook快速導出百萬條數據呢?
- 首先使用造好的輪子:hutool的ExcelUti
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.3.1</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.17</version> </dependency>
PS. Hutool這個工具很好,錯過一定會後悔
Hutool是一個小而全的Java工具類庫,通過靜態方法封裝,降低相關API的學習成本,提高工作效率,使Java擁有函數式語言般的優雅,讓Java語言也可以“甜甜的”。
Hutool中的工具方法來自於每個用戶的精雕細琢,它涵蓋了Java開發底層代碼中的方方面面,它既是大型項目開發中解決小問題的利器,也是小型項目中的效率擔當;
Hutool是項目中“util”包友好的替代,它節省了開發人員對項目中公用類和公用工具方法的封裝時間,使開發專注於業務,同時可以最大限度的避免封裝不完善帶來的bug。
- 測試代碼
public static void main(String[] args) { //耗時計算 TimeInterval timer = DateUtil.timer(); List<List<Integer>> rows = CollUtil.newArrayList(); for (int i = 0; i < 1000000; i++) { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); rows.add(list); } BigExcelWriter writer = ExcelUtil.getBigWriter("e:/test3.xlsx"); // 一次性寫出內容,使用默認樣式 writer.write(rows); // 關閉writer, writer.close(); System.out.println("執行時間:"+timer.intervalSecond()); }
執行導出100w條數據耗時17s ,注意數據量最大行數:1048576
- 我的結合Hutool在業務中使用
/** * 導出excel * * @param response * @param name 文件名 * @param rows 業務數據 ArrayList<Map<String, Object>> */ public static void writeExcel(HttpServletResponse response, String name, List<Map<String, Object>> rows) { // 通過工具類創建writer ExcelWriter writer = cn.hutool.poi.excel.ExcelUtil.getBigWriter(); // 一次性寫出內容,使用默認樣式 writer.write(rows, true); write(response, name, writer); } /** * 下載 * * @param response * @param name * @param writer */ public static void write(HttpServletResponse response, String name, ExcelWriter writer) { // response爲HttpServletResponse對象 response.setContentType("application/vnd.ms-excel;charset=utf-8"); String fileName = name + ".xls"; ServletOutputStream out = null; try { // test.xls是彈出下載對話框的文件名,不能爲中文,中文請自行編碼 response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1")); out = response.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } writer.flush(out); // 關閉writer,釋放內存 writer.close(); }
//查出商品數據 Pager<RespCommoditySalesVo> commoditySalesVos = commoditySalesService.commoditySalesRealTime(suParamsVo); List<Map<String, Object>> rows = CollUtil.newArrayList(); commoditySalesVos.getContent().forEach(commoditySales -> { Map<String, Object> row = new LinkedHashMap<>(); //標題-數據 row.put("商品名稱", commoditySales.getName()); row.put("規格", commoditySales.getWeight()); row.put("實時銷量", commoditySales.getSales()); row.put("剩餘可售庫存", commoditySales.getInventory()); row.put("物料號", commoditySales.getSku()); row.put("成本", commoditySales.getCostPrice()); row.put("上下架", CommodityShelvesEnum.getValue(commoditySales.getShelves())); rows.add(row); }); ExcelUtil.writeExcel(response, "商品實時銷量", rows);
查詢出業務數據組裝在map裏,key爲標題,value爲對應數據,然後調用ExcelUtil的write方法寫出到excel;
-
具體業務參數格式可以參照 https://hutool.cn/docs/#/poi/Excel%E7%94%9F%E6%88%90-ExcelWriter
爲什麼使用SXSSFWorkbook這麼快:
這是它的官方文檔,我就不贅述了:https://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api
懶得點鏈接看的話,這是谷歌翻譯:
SXSSF(軟件包:org.apache.poi.xssf.streaming)是XSSF的API兼容流擴展,可用於必須生成非常大的電子表格且堆空間有限的情況。SXSSF通過限制對滑動窗口內的行的訪問來實現其低內存佔用,而XSSF允許對文檔中的所有行進行訪問。不再在窗口中的較舊的行將被寫入磁盤,因此無法訪問。
您可以在工作簿構建時通過新的SXSSFWorkbook(int windowSize)指定窗口大小, 也可以通過SXSSFSheet#setRandomAccessWindowSize(int windowSize)在每張紙上設置窗口大小。
當通過createRow()創建新行並且未刷新記錄的總數超過指定的窗口大小時,索引值最低的行將被刷新,並且無法再通過getRow()進行訪問。
默認窗口大小爲100,由SXSSFWorkbook.DEFAULT_WINDOW_SIZE定義。
windowSize -1表示訪問不受限制。在這種情況下,所有未通過調用flushRows()進行刷新的記錄都可以用於隨機訪問。
請注意,SXSSF分配臨時文件,您必須始終通過調用dispose方法來明確清理這些文件。
SXSSFWorkbook默認使用內聯字符串而不是共享字符串表。這是非常有效的,因爲不需要將文檔內容保留在內存中,但是衆所周知,生成的文檔與某些客戶端不兼容。啓用共享字符串後,文檔中的所有唯一字符串都必須保留在內存中。與禁用共享字符串相比,根據您的文檔內容,這可能會使用更多的資源。
請注意,根據您正在使用的功能,有些東西仍然可能會佔用大量內存,例如,合併區域,超鏈接,註釋等仍然僅存儲在內存中,因此,如果廣泛使用。
在決定是否啓用共享字符串之前,請仔細檢查您的內存預算和兼容性需求。