你一定沒用過最簡單的使用SXSSFWorkbook快速導出百萬條數據

常見的導出可能上百萬甚至千萬的數據量業務場景

  1. 歷史訂單的導出
  2. 歷史訂單明細的導出
  3. 歷史支付明細的導出
  4. 用戶信息的導出
  5. 等等等

遇到這些問題我想你一定頭疼過,客服或財務可能會在你睡覺的時候找上你

財務: “大王趕快起來看下系統,系統又卡住了”

大王:“你小子又幹啥了”

財務: “我剛導出了一個月的訂單明細”

大王:“那應該,沒啥問題呀,我們一個月撐死也就1w筆訂單”

財務: “上個月不是搞了個活動嗎,現在有100w筆訂單,然後就導不出來了”

大王默默心裏吐槽,但還是從牀上爬起來,打開日誌一看,果然是這個問題:“內存溢出”

  1. 因爲往常訂單少,根據時間查詢訂單沒有分頁,大量數據查詢超時
  2. 之前使用XSSFWorkbook下載的時候大量數據轉換的輸出流內存溢出

解決方案

  1. 對大量數據進行分頁查詢
  2. 導出方法使用SXSSFWorkbook解決

分頁就不說了,自己實現

那如何最簡單的使用SXSSFWorkbook快速導出百萬條數據呢?

  1. 首先使用造好的輪子: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。 

  2. 測試代碼
    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

  3. 我的結合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;

  4. 具體業務參數格式可以參照 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默認使用內聯字符串而不是共享字符串表。這是非常有效的,因爲不需要將文檔內容保留在內存中,但是衆所周知,生成的文檔與某些客戶端不兼容。啓用共享字符串後,文檔中的所有唯一字符串都必須保留在內存中。與禁用共享字符串相比,根據您的文檔內容,這可能會使用更多的資源。

請注意,根據您正在使用的功能,有些東西仍然可能會佔用大量內存,例如,合併區域,超鏈接,註釋等仍然僅存儲在內存中,因此,如果廣泛使用。

在決定是否啓用共享字符串之前,請仔細檢查您的內存預算和兼容性需求。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章