1 EasyExcel簡介
EasyExcel是一個基於Java的簡單、省內存的讀寫Excel的開源項目。在儘可能節約內存的情況下支持讀寫百M的Excel。
github地址: https://github.com/alibaba/easyexcel
官方文檔: https://www.yuque.com/easyexcel/doc/easyexcel
Excel解析流程圖:
EasyExcel讀取Excel的解析原理:
2 EasyExcel使用
2.1 EasyExcel相關依賴
添加maven依賴, 依賴的poi最低版本3.17
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.3</version> </dependency>
2.2 讀Excel
2.2.1 最簡單的讀(方式一)
Excel數據類型
字符串標題 | 日期標題 | 數字標題 |
---|---|---|
小明 | 2020-05-05 10:10:10 | 888.88 |
數據模板
注意: Java類中的屬性字段順序和Excel中的表頭字段順序一致, 可以不寫@ExcelProperty
@NoArgsConstructor @AllArgsConstructor @Data @Builder public class DemoData { // 根據Excel中指定列名或列的索引讀取 @ExcelProperty(value = "字符串標題", index = 0) private String name; @ExcelProperty(value = "日期標題", index = 1) private Date hireDate; @ExcelProperty(value = "數字標題", index = 2) private Double salary; // lombok 會生成getter/setter方法 }
讀取excel代碼
關鍵是寫一個監聽器,實現AnalysisEventListener, 每解析一行數據會調用invoke方法返回解析的數據, 當全部解析完成後會調用doAfterAllAnalysed方法. 我們重寫invoke方法和doAfterAllAnalysed方法即可.
@Test public void testReadExcel() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\read.xlsx"; // 讀取excel EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() { // 每解析一行數據,該方法會被調用一次 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("解析數據爲:" + demoData.toString()); } // 全部解析完成被調用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("解析完成..."); // 可以將解析的數據保存到數據庫 } }).sheet().doRead(); }
效果:
2.2.2 最簡單的讀(方式二)
讀excel的方式二代碼
@Test public void testReadExcel2() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\read.xlsx"; // 創建一個數據格式來裝讀取到的數據 Class<DemoData> head = DemoData.class; // 創建ExcelReader對象 ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() { // 每解析一行數據,該方法會被調用一次 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("解析數據爲:" + demoData.toString()); } // 全部解析完成被調用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("解析完成..."); // 可以將解析的數據保存到數據庫 } }).build(); // 創建sheet對象,並讀取Excel的第一個sheet(下標從0開始), 也可以根據sheet名稱獲取 ReadSheet sheet = EasyExcel.readSheet(0).build(); // 讀取sheet表格數據, 參數是可變參數,可以讀取多個sheet excelReader.read(sheet); // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉 excelReader.finish(); }
2.2.3 格式化Excel中的數據格式
要讀取的源數據, 日期格式是yyyy年MM月dd日 HH時mm分ss秒, 數字帶小數點
數據模板
@NoArgsConstructor @AllArgsConstructor @Data @Builder public class DemoData { @ExcelProperty(value = "字符串標題", index = 0) private String name; @ExcelProperty(value = "日期標題", index = 1) // 格式化日期類型數據 @DateTimeFormat(value = "yyyy年MM月dd日 HH時mm分ss秒") private Date hireDate; @ExcelProperty(value = "數字標題", index = 2) // 格式化數字類型數據,保留一位小數 @NumberFormat(value = "###.#") private String salary; //注意: @NumberFormat對於Double類型的數據格式化會失效,建議使用String類型接收數據進行格式化 // private Double salary; // lombok 會生成getter/setter方法 }
讀取excel代碼同上面讀取方式一樣.
2.2.4 讀取多個sheet表格
2.2.4.1 讀所有sheet
讀方式一, 使用ExcelReaderBuilder#doReadAll方法
@Test public void testReadExcel() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\read.xlsx"; // 讀取excel EasyExcel.read(filename, DemoData.class, new AnalysisEventListener<DemoData>() { // 每解析一行數據,該方法會被調用一次 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("解析數據爲:" + demoData.toString()); } // 全部解析完成被調用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("解析完成..."); // 可以將解析的數據保存到數據庫 } }) // .sheet(0).doRead(); .doReadAll(); // 讀取全部sheet }
讀方式二, 使用ExcelReader#readAll方法
@Test public void testReadExcel2() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\read.xlsx"; // 創建一個數據格式來裝讀取到的數據 Class<DemoData> head = DemoData.class; // 創建ExcelReader對象 ExcelReader excelReader = EasyExcel.read(filename, head, new AnalysisEventListener<DemoData>() { // 每解析一行數據,該方法會被調用一次 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("解析數據爲:" + demoData.toString()); } // 全部解析完成被調用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("解析完成..."); // 可以將解析的數據保存到數據庫 } }).build(); // 創建sheet對象,並讀取Excel的第一個sheet(下標從0開始), 也可以根據sheet名稱獲取 ReadSheet sheet = EasyExcel.readSheet(0).build(); // 讀取sheet表格數據 , 參數是可變參數,可以讀取多個sheet // excelReader.read(sheet); excelReader.readAll(); // 讀所有sheet // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉 excelReader.finish(); }
2.2.4.2 讀指定的多個sheet
不同sheet表格的數據模板可能不一樣,這時候就需要分別構建不同的sheet對象,分別爲其指定對應的數據模板.
@Test- public void testReadExcel3() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\read.xlsx"; // 構建ExcelReader對象 ExcelReader excelReader = EasyExcel.read(filename).build(); // 構建sheet對象 ReadSheet sheet0 = EasyExcel.readSheet(0) .head(DemoData.class) // 指定sheet0的數據模板 .registerReadListener(new AnalysisEventListener<DemoData>() { // 每解析一行數據,該方法會被調用一次 @Override public void invoke(DemoData demoData, AnalysisContext analysisContext) { System.out.println("解析數據爲:" + demoData.toString()); } // 全部解析完成被調用 @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { System.out.println("解析完成..."); // 可以將解析的數據保存到數據庫 } }).build(); // 讀取sheet,有幾個就構建幾個sheet進行讀取 excelReader.read(sheet0); // 需要自己關閉流操作,在讀取文件時會創建臨時文件,如果不關閉,會損耗磁盤,嚴重的磁盤爆掉 excelReader.finish(); }
3 EasyExcel使用優化
3.1 監聽器優化
上面章節的讀取Excel的程序弊端:
每次解析不同數據模型都要新增一個監聽器, 重複工作量大;
即使用了匿名內部類,程序也顯得臃腫;
數據處理一般都會存在於項目的service中, 監聽器難免會依賴dao層, 導致程序耦合度高.
解決方案:
通過泛型指定數據模型類型, 針對不同類型的數據模型只需要定義一個監聽器即可;
使用jdk8新特性中的函數式接口, 將數據處理從監聽器中剝離出去, 進行解耦.
監聽器代碼:
/** * 類描述:easyexcel工具類 * @Author * @Date * @Version 1.0 */ public class EasyExcelUtils<T> { /** * 獲取讀取Excel的監聽器對象 * 爲了解耦及減少每個數據模型bean都要創建一個監聽器的臃腫, 使用泛型指定數據模型類型 * 使用jdk8新特性中的函數式接口 Consumer * 可以實現任何數據模型bean的數據解析, 不用重複定義監聽器 * @param consumer 處理解析數據的函數, 一般可以是數據入庫邏輯的函數 * @param threshold 閾值,達到閾值就處理一次存儲的數據 * @param <T> 數據模型泛型 * @return 返回監聽器 */ public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer, int threshold) { return new AnalysisEventListener<T>() { /** * 存儲解析的數據 T t */ // ArrayList基於數組實現, 查詢更快 // List<T> dataList = new ArrayList<>(threshold); // LinkedList基於雙向鏈表實現, 插入和刪除更快 List<T> dataList = new LinkedList<>(); /** * 每解析一行數據事件調度中心都會通知到這個方法, 訂閱者1 * @param data 解析的每行數據 * @param context */ @Override public void invoke(T data, AnalysisContext context) { dataList.add(data); // 達到閾值就處理一次存儲的數據 if (dataList.size() >= threshold) { consumer.accept(dataList); dataList.clear(); } } /** * excel文件解析完成後,事件調度中心會通知到該方法, 訂閱者2 * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 最後閾值外的數據做處理 if (dataList.size() > 0) { consumer.accept(dataList); } } }; } /** * 獲取讀取Excel的監聽器對象, 不指定閾值, 默認閾值爲 2000 * @param consumer * @param <T> * @return */ public static <T> AnalysisEventListener<T> getReadListener(Consumer<List<T>> consumer) { return getReadListener(consumer, 2000); } }
再來看讀取Excel的 代碼:
/** * 採用解耦的自定義監聽器讀取Excel, 可以實現任何數據模型bean的讀取 */ @Test public void testReadExcelN() { // 讀取的excel文件路徑 String filename = "D:\\study\\excel\\user1.xlsx"; // 讀取excel EasyExcel.read(filename, UserModel.class, EasyExcelUtils.getReadListener(dataProcess())) .doReadAll(); // 讀取全部sheet } /** * 傳給監聽器的是一個處理解析數據的函數, 當調用consumer的accept方法時就會調用傳遞的函數邏輯 * 這裏傳遞的函數是對解析結果集的遍歷打印操作, 也可以是數據入庫操作 * @return */ public Consumer<List<UserModel>> dataProcess() { Consumer<List<UserModel>> consumer = users -> users.forEach(System.out::println); return consumer; }
參考網址:
https://blog.csdn.net/u013044713/article/details/120249233
https://github.com/alibaba/easyexcel
https://www.jianshu.com/p/10e05e89b8a0
http://events.jianshu.io/p/52d9e12e93bd