tft每日頭條

 > 生活

 > springboot導出excel教程

springboot導出excel教程

生活 更新时间:2024-09-13 12:33:02
一、簡介

各位網友,大家好,我是阿粉!

在實際的業務系統開發過程中,操作 Excel 實現數據的導入導出基本上是個非常常見的需求。

之前,我們有介紹一款非常好用的工具:EasyPoi,有讀者提出在數據量大的情況下,EasyPoi 會占用内存大,性能不夠好,嚴重的時候,還會出現内存異常的現象。

今天我給大家推薦一款性能更好的 Excel 導入導出工具:EasyExcel,希望對大家有所幫助!

easyexcel 是阿裡開源的一款 excel導入導出工具,具有處理速度快、占用内存小、使用方便的特點,底層邏輯也是基于 apache poi 進行二次開發的,目前的應用也是非常廣!

springboot導出excel教程(SpringBoot實現Excel自由導入導出性能強的很)1

相比 EasyPoi,EasyExcel 的處理數據性能非常高,讀取 75M (46W行25列) 的Excel,僅需使用 64M 内存,耗時 20s,極速模式還可以更快!

springboot導出excel教程(SpringBoot實現Excel自由導入導出性能強的很)2

廢話也不多說了,下面直奔主題!

二、實踐

在 SpringBoot 項目中集成 EasyExcel 其實非常簡單,僅需一個依賴即可。

<!--EasyExcel相關依賴--> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.0.5</version> </dependency>

EasyExcel 的導出導入支持兩種方式進行處理

  • 第一種是通過實體類注解方式來生成文件和反解析文件數據映射成對象
  • 第二種是通過動态參數化生成文件和反解析文件數據

下面我們以用戶信息的導出導入為例,分别介紹兩種處理方式。

簡單導出

首先,我們隻需要創建一個UserEntity用戶實體類,然後添加對應的注解字段即可,示例代碼如下:

public class UserWriteEntity { @ExcelProperty(value = "姓名") private String name; @ExcelProperty(value = "年齡") private int age; @DateTimeFormat("yyyy-MM-dd HH:mm:ss") @ExcelProperty(value = "操作時間") private Date time; //set、get... }

然後,使用 EasyExcel 提供的EasyExcel工具類,即可實現文件的導出。

public static void main(String[] args) throws FileNotFoundException { List<UserWriteEntity> dataList = new ArrayList<>(); for (int i = 0; i < 10; i ) { UserWriteEntity userEntity = new UserWriteEntity(); userEntity.setName("張三" i); userEntity.setAge(20 i); userEntity.setTime(new Date(System.currentTimeMillis() i)); dataList.add(userEntity); } //定義文件輸出位置 FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user1.xlsx")); EasyExcel.write(outputStream, UserWriteEntity.class).sheet("用戶信息").doWrite(dataList); }

運行程序,打開文件内容結果!

springboot導出excel教程(SpringBoot實現Excel自由導入導出性能強的很)3

簡單導入

這種簡單固定表頭的 Excel 文件,如果想要讀取文件數據,操作也很簡單。

以上面的導出文件為例,使用 EasyExcel 提供的EasyExcel工具類,即可來實現文件内容數據的快速讀取,示例代碼如下:

首先創建讀取實體類

/** * 讀取實體類 */ public class UserReadEntity { @ExcelProperty(value = "姓名") private String name; /** * 強制讀取第三個 這裡不建議 index 和 name 同時用,要麼一個對象隻用index,要麼一個對象隻用name去匹配 */ @ExcelProperty(index = 1) private int age; @DateTimeFormat("yyyy-MM-dd HH:mm:ss") @ExcelProperty(value = "操作時間") private Date time; //set、get... }

然後讀取文件數據,并封裝到對象裡面

public static void main(String[] args) throws FileNotFoundException { //同步讀取文件内容 FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls")); List<UserReadEntity> list = EasyExcel.read(inputStream).head(UserReadEntity.class).sheet().doReadSync(); System.out.println(JSONArray.toJSONString(list)); }

運行程序,輸出結果如下:

[{"age":20,"name":"張三0","time":1616920360000},{"age":21,"name":"張三1","time":1616920360000},{"age":22,"name":"張三2","time":1616920360000},{"age":23,"name":"張三3","time":1616920360000},{"age":24,"name":"張三4","time":1616920360000},{"age":25,"name":"張三5","time":1616920360000},{"age":26,"name":"張三6","time":1616920360000},{"age":27,"name":"張三7","time":1616920360000},{"age":28,"name":"張三8","time":1616920360000},{"age":29,"name":"張三9","time":1616920360000}]

動态自由導出導入

在實際使用開發中,我們不可能每來一個 excel 導入導出需求,就編寫一個實體類,很多業務需求需要根據不同的字段來動态導入導出,沒辦法基于實體類注解的方式來讀取文件或者寫入文件。

因此,基于EasyExcel提供的動态參數化生成文件和動态監聽器讀取文件方法,我們可以單獨封裝一套動态導出導出工具類,省的我們每次都需要重新編寫大量重複工作,以下就是小編我在實際使用過程,封裝出來的工具類,在此分享給大家!

  • 首先,我們可以編寫一個動态導出工具類

public class DynamicEasyExcelExportUtils { private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtils.class); private static final String DEFAULT_SHEET_NAME = "sheet1"; /** * 動态生成導出模版(單表頭) * @param headColumns 列名稱 * @return excel文件流 */ public static byte[] exportTemplateExcelFile(List<String> headColumns){ List<List<String>> excelHead = Lists.newArrayList(); headColumns.forEach(columnName -> { excelHead.add(Lists.newArrayList(columnName)); }); byte[] stream = createExcelFile(excelHead, new ArrayList<>()); return stream; } /** * 動态生成模版(複雜表頭) * @param excelHead 列名稱 * @return */ public static byte[] exportTemplateExcelFileCustomHead(List<List<String>> excelHead){ byte[] stream = createExcelFile(excelHead, new ArrayList<>()); return stream; } /** * 動态導出文件(通過map方式計算) * @param headColumnMap 有序列頭部 * @param dataList 數據體 * @return */ public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){ //獲取列名稱 List<List<String>> excelHead = new ArrayList<>(); if(MapUtils.isNotEmpty(headColumnMap)){ //key為匹配符,value為列名,如果多級列名用逗号隔開 headColumnMap.entrySet().forEach(entry -> { excelHead.add(Lists.newArrayList(entry.getValue().split(","))); }); } List<List<Object>> excelRows = new ArrayList<>(); if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){ for (Map<String, Object> dataMap : dataList) { List<Object> rows = new ArrayList<>(); headColumnMap.entrySet().forEach(headColumnEntry -> { if(dataMap.containsKey(headColumnEntry.getKey())){ Object data = dataMap.get(headColumnEntry.getKey()); rows.add(data); } }); excelRows.add(rows); } } byte[] stream = createExcelFile(excelHead, excelRows); return stream; } /** * 生成文件(自定義頭部排列) * @param rowHeads * @param excelRows * @return */ public static byte[] customerExportExcelFile(List<List<String>> rowHeads, List<List<Object>> excelRows){ //将行頭部轉成easyexcel能識别的部分 List<List<String>> excelHead = transferHead(rowHeads); return createExcelFile(excelHead, excelRows); } /** * 生成文件 * @param excelHead * @param excelRows * @return */ private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){ try { if(CollectionUtils.isNotEmpty(excelHead)){ ByteArrayOutputStream outputStream = new ByteArrayoutputStream(); EasyExcel.write(outputStream).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) .head(excelHead) .sheet(DEFAULT_SHEET_NAME) .doWrite(excelRows); return outputStream.toByteArray(); } } catch (Exception e) { log.error("動态生成excel文件失敗,headColumns:" JSONArray.toJSONString(excelHead) ",excelRows:" JSONArray.toJSONString(excelRows), e); } return null; } /** * 将行頭部轉成easyexcel能識别的部分 * @param rowHeads * @return */ public static List<List<String>> transferHead(List<List<String>> rowHeads){ //将頭部列進行反轉 List<List<String>> realHead = new ArrayList<>(); if(CollectionUtils.isNotEmpty(rowHeads)){ Map<Integer, List<String>> cellMap = new LinkedHashMap<>(); //遍曆行 for (List<String> cells : rowHeads) { //遍曆列 for (int i = 0; i < cells.size(); i ) { if(cellMap.containsKey(i)){ cellMap.get(i).add(cells.get(i)); } else { cellMap.put(i, Lists.newArrayList(cells.get(i))); } } } //将列一行一行加入realHead cellMap.entrySet().forEach(item -> realHead.add(item.getValue())); } return realHead; } /** * 導出文件測試 * @param args * @throws IOException */ public static void main(String[] args) throws IOException { //導出包含數據内容的文件(方式一) LinkedHashMap<String, String> headColumnMap = Maps.newLinkedHashMap(); headColumnMap.put("className","班級"); headColumnMap.put("name","學生信息,姓名"); headColumnMap.put("sex","學生信息,性别"); List<Map<String, Object>> dataList = new ArrayList<>(); for (int i = 0; i < 5; i ) { Map<String, Object> dataMap = Maps.newHashMap(); dataMap.put("className", "一年級"); dataMap.put("name", "張三" i); dataMap.put("sex", "男"); dataList.add(dataMap); } byte[] stream1 = exportExcelFile(headColumnMap, dataList); FileOutputStream outputStream1 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx")); outputStream1.write(stream1); outputStream1.close(); //導出包含數據内容的文件(方式二) //頭部,第一層 List<String> head1 = new ArrayList<>(); head1.add("第一行頭部列1"); head1.add("第一行頭部列1"); head1.add("第一行頭部列1"); head1.add("第一行頭部列1"); //頭部,第二層 List<String> head2 = new ArrayList<>(); head2.add("第二行頭部列1"); head2.add("第二行頭部列1"); head2.add("第二行頭部列2"); head2.add("第二行頭部列2"); //頭部,第三層 List<String> head3 = new ArrayList<>(); head3.add("第三行頭部列1"); head3.add("第三行頭部列2"); head3.add("第三行頭部列3"); head3.add("第三行頭部列4"); //封裝頭部 List<List<String>> allHead = new ArrayList<>(); allHead.add(head1); allHead.add(head2); allHead.add(head3); //封裝數據體 //第一行數據 List<Object> data1 = Lists.newArrayList(1,1,1,1); //第二行數據 List<Object> data2 = Lists.newArrayList(2,2,2,2); List<List<Object>> allData = Lists.newArrayList(data1, data2); byte[] stream2 = customerExportExcelFile(allHead, allData); FileOutputStream outputStream2 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user6.xlsx")); outputStream2.write(stream2); outputStream2.close(); } }

  • 然後,編寫一個動态導入工具類

/** * 創建一個文件讀取監聽器 */ public class DynamicEasyExcelListener extends AnalysisEventListener<Map<Integer, String>> { private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class); /** * 表頭數據(存儲所有的表頭數據) */ private List<Map<Integer, String>> headList = new ArrayList<>(); /** * 數據體 */ private List<Map<Integer, String>> dataList = new ArrayList<>(); /** * 這裡會一行行的返回頭 * * @param headMap * @param context */ @Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { LOGGER.info("解析到一條頭數據:{}", JSON.toJSONString(headMap)); //存儲全部表頭數據 headList.add(headMap); } /** * 這個每一條數據解析都會來調用 * * @param data * one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { LOGGER.info("解析到一條數據:{}", JSON.toJSONString(data)); dataList.add(data); } /** * 所有數據解析完成了 都會來調用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 這裡也要保存數據,确保最後遺留的數據也存儲到數據庫 LOGGER.info("所有數據解析完成!"); } public List<Map<Integer, String>> getHeadList() { return headList; } public List<Map<Integer, String>> getDataList() { return dataList; } }

  • 動态導入工具類

/** * 編寫導入工具類 */ public class DynamicEasyExcelImportUtils { /** * 動态獲取全部列和數據體,默認從第一行開始解析數據 * @param stream * @return */ public static List<Map<String,String>> parseExcelToView(byte[] stream) { return parseExcelToView(stream, 1); } /** * 動态獲取全部列和數據體 * @param stream excel文件流 * @param parseRowNumber 指定讀取行 * @return */ public static List<Map<String,String>> parseExcelToView(byte[] stream, Integer parseRowNumber) { DynamicEasyExcelListener readListener = new DynamicEasyExcelListener(); EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead(); List<Map<Integer, String>> headList = readListener.getHeadList(); if(CollectionUtils.isEmpty(headList)){ throw new RuntimeException("Excel未包含表頭"); } List<Map<Integer, String>> dataList = readListener.getDataList(); if(CollectionUtils.isEmpty(dataList)){ throw new RuntimeException("Excel未包含數據"); } //獲取頭部,取最後一次解析的列頭數據 Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() -1); //封裝數據體 List<Map<String,String>> excelDataList = Lists.newArrayList(); for (Map<Integer, String> dataRow : dataList) { Map<String,String> rowData = new LinkedHashMap<>(); excelHeadIdxNameMap.entrySet().forEach(columnHead -> { rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey())); }); excelDataList.add(rowData); } return excelDataList; } /** * 文件導入測試 * @param args * @throws IOException */ public static void main(String[] args) throws IOException { FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx")); byte[] stream = IoUtils.toByteArray(inputStream); List<Map<String,String>> dataList = parseExcelToView(stream, 2); System.out.println(JSONArray.toJSONString(dataList)); inputStream.close(); } }

為了方便後續的操作流程,在解析數據的時候,會将列名作為key!

三、小結

在實際的業務開發過程中,根據參數動态實現 Excel 的導出導入還是非常廣的。

當然,EasyExcel 的功能還不隻上面介紹的那些内容,還有基于模版進行 excel的填充,web 端 restful 的導出導出,使用方法大緻都差不多,更多的功能,會在後續的文章中再次介紹,如果有描述不對的地方,歡迎網友批評吐槽!

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved