一、Hutool介紹
Hutool是一個小而全的Java工具類庫,通過靜態方法封裝,降低相關API的學習成本,提高工作效率,使Java擁有函數式語言般的優雅,讓Java語言也可以「甜甜的」。
Hutool是項目中「util」包友好的替代,它節省了開發人員對項目中公用類和公用工具方法的封裝時間,使開發專注於業務,同時可以最大限度地避免封裝不完善帶來的bug。
今天要講的是excel的導出並合併單元格,其他工具類,可查看參考文檔,之後也會陸續的更新一些常用工具類的用法。
二、背景
之前常用的是Apache的POI庫,代碼複雜、非常耗內存,嚴重時會導致內存溢出,並發上來,一定會OOM或頻繁full gc,後來發現平時使用的Hutool工具類,也有excel的導入導出API,使用起來簡單方便。Hutool-poi是針對Apache POI的封裝,因此需要用戶自行引入POI庫,Hutool默認不引入
三、excel普通導出
引入依賴:
推薦引入poi-ooxml,這個包會自動關聯引入poi包,且可以很好的支持Office2007+的文檔格式
普通導出
/*** @Author zmy * @Description 普通導出excel * @Date 11:01 2020/12/16 * @param response 響應 * @param fileName excel文件名 * @param sheetName sheet名稱 * @param headers 表頭別名map,有序maplinkHashMap,才能保證表頭是有序的 * @param data 數據集合 * @return void */ public void exportExcelByHutools(HttpServletResponse response, String fileName, String sheetName, Map<String, String> headers, List<T> data) { //通過工具類創建writer try { //對於大量數據輸出,採用bigwriter,其他方法不變 // ExcelWriter writer = ExcelUtil.getBigWriter(); ExcelWriter writer = ExcelUtil.getWriter(); //設置sheet的名稱 writer.renameSheet(sheetName); //設置head的名稱, 此時的順序就是導出的順序, key就是屬性名稱, value就是別名 headers.entrySet().forEach(entry -> { //這個添加順序和導出順序相同 writer.addHeaderAlias(entry.getKey(), entry.getValue()); }); writer.write(data, true); response.reset(); response.setContentType("application/vnd.ms-excel;charset=utf-8"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx"); writer.flush(response.getOutputStream()); writer.close(); } catch (Exception e) { //如果導出異常,則生成一個空的文件 log.info("######導出 excel異常 :{}", e.getMessage()); } }
其他導出方法可參考GitHub
四、excel導出並合併單元格
今天重點是合併單元格,最近遇到導出excel需要自定義欄位合併單元格,網上查了一些,資料比較少,有些是針對特定項目的邏輯處理,沒有一個通用的方法,於是自己就著手寫了一個
支持自定義表頭別名,合併單元格,創建writer的方法可根據業務需要進行修改。
/*** @Author zmy * @Description hutool導出excel * @Date 10:28 2020/12/16 * @param dataList 數據集合 * @param fileName excel文件名 * @param headAliasMap 表頭別名 * @param mergeNameColumnIndexMap 合併單元格的欄位名和列角標,角標從0開始 * @param isMerge 是否需要合併單元格 * @return void */ public static void hutoolExportExcel(List<?> dataList, String fileName, LinkedHashMap<String, String> headAliasMap, LinkedHashMap<String, Integer> mergeNameColumnIndexMap, Boolean isMerge) throws Exception { // 通過工具類創建writer ExcelWriter writer = ExcelUtil.getWriter(fileName); //自定義標題別名 if (MapUtil.isNotEmpty(headAliasMap)) { writer.setHeaderAlias(headAliasMap); } if (isMerge) { //獲取需要合併的單元格所對應的行集合 Map<Integer, List<RowRangeDto>> stringListMap = addMerStrategy(dataList, mergeNameColumnIndexMap); //調用merge合併單元格 layout(writer, stringListMap); } // 一次性寫出內容,使用默認樣式,強制輸出標題 writer.write(dataList, true); // flush或者close方法後才會真正寫出文件;關閉writer,才會釋放Workbook對象資源 writer.close(); }
addMerStrategy:獲取合併單元格對應的行集合map
/*** @Author zmy * @Description 獲取合併單元格對應的行集合 * @Date 11:16 2020/12/16 * @param dataList 數據集合 * @param mergeNameColumnIndexMap 有序map,合併單元格欄位和列角標map * @return 合併單元格對應的行集合映射 */ public static Map<Integer, List<RowRangeDto>> addMerStrategy(List<?> dataList, LinkedHashMap<String, Integer> mergeNameColumnIndexMap) throws Exception { Map<Integer, List<RowRangeDto>> strategyMap = new HashMap<>(); Object preObj = null; int i = 0; for (Object currObj : dataList) { if (preObj != null) { Boolean mergeFlag = false; int j = 0; for (Map.Entry<String, Integer> entry : mergeNameColumnIndexMap.entrySet()) { //在第一個列合併的情況下,後面的列才需要合併 if (mergeFlag || j == 0) { String name = entry.getKey(); if (getGetMethod(currObj, name).equals(getGetMethod(preObj, name))) { fillStrategyMap(strategyMap, mergeNameColumnIndexMap.get(name), i); mergeFlag = true; } } else { break; } j++; } } i++; preObj = currObj; } return strategyMap; }
getGetMethod:根據方法獲取get方法
fillStrategyMap:獲取合併單元格對應的行集合
layout:調用merge,合併單元格
RowRangeDto:起始截止行對象