您好,登錄后才能下訂單哦!
今天小編給大家分享一下EasyExcel怎么引用的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
導出數據到Excel
是非常常見的后端需求之一,今天來推薦一款阿里出品的Excel
操作神器:EasyExcel
。EasyExcel
從其依賴樹來看是對apache-poi
的封裝,筆者從開始接觸Excel
處理就選用了EasyExcel
,避免了廣泛流傳的apache-poi
導致的內存泄漏問題。
引入EasyExcel
的Maven
如下:
com.alibaba easyexcel ${easyexcel.version}
當前(2020-09
)的最新版本為2.2.6
。
Excel
文件主要圍繞讀和寫操作進行處理,EasyExcel
的API
也是圍繞這兩個方面進行設計。先看讀操作的相關API
:
// 新建一個ExcelReaderBuilder實例 ExcelReaderBuilder readerBuilder = EasyExcel.read(); // 讀取的文件對象,可以是File、路徑(字符串)或者InputStream實例 readerBuilder.file(""); // 文件的密碼 readerBuilder.password(""); // 指定sheet,可以是數字序號sheetNo或者字符串sheetName,若不指定則會讀取所有的sheet readerBuilder.sheet(""); // 是否自動關閉輸入流 readerBuilder.autoCloseStream(true); // Excel文件格式,包括ExcelTypeEnum.XLSX和ExcelTypeEnum.XLS readerBuilder.excelType(ExcelTypeEnum.XLSX); // 指定文件的標題行,可以是Class對象(結合@ExcelProperty注解使用),或者List實例 readerBuilder.head(Collections.singletonList(Collections.singletonList("head"))); // 注冊讀取事件的監聽器,默認的數據類型為Map,第一列的元素的下標從0開始 readerBuilder.registerReadListener(new AnalysisEventListener() { @Override public void invokeHeadMap(Map headMap, AnalysisContext context) { // 這里會回調標題行,文件內容的首行會認為是標題行 } @Override public void invoke(Object o, AnalysisContext analysisContext) { // 這里會回調每行的數據 } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { } }); // 構建讀取器 ExcelReader excelReader = readerBuilder.build(); // 讀取數據 excelReader.readAll(); excelReader.finish();
可以看到,讀操作主要使用Builder
模式和事件監聽(或者可以理解為「觀察者模式」)的設計。一般情況下,上面的代碼可以簡化如下:
Map head = new HashMap(); List data = new LinkedList(); EasyExcel.read("文件的絕對路徑").sheet() .registerReadListener(new AnalysisEventListener() { @Override public void invokeHeadMap(Map headMap, AnalysisContext context) { head.putAll(headMap); } @Override public void invoke(Map row, AnalysisContext analysisContext) { data.add(row); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 這里可以打印日志告知所有行讀取完畢 } }).doRead();
如果需要讀取數據并且轉換為對應的對象列表,則需要指定標題行的Class
,結合注解@ExcelProperty
使用:
文件內容: |訂單編號|手機號| |ORDER_ID_1|112222| |ORDER_ID_2|334455| @Data private static class OrderDTO { @ExcelProperty(value = "訂單編號") private String orderId; @ExcelProperty(value = "手機號") private String phone; } Map head = new HashMap(); List data = new LinkedList(); EasyExcel.read("文件的絕對路徑").head(OrderDTO.class).sheet() .registerReadListener(new AnalysisEventListener() { @Override public void invokeHeadMap(Map headMap, AnalysisContext context) { head.putAll(headMap); } @Override public void invoke(OrderDTO row, AnalysisContext analysisContext) { data.add(row); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { // 這里可以打印日志告知所有行讀取完畢 } }).doRead();
「如果數據量巨大,建議使用Map
類型讀取和操作數據對象,否則大量的反射操作會使讀取數據的耗時大大增加,極端情況下,例如屬性多的時候反射操作的耗時有可能比讀取和遍歷的時間長」。
接著看寫操作的API
:
// 新建一個ExcelWriterBuilder實例 ExcelWriterBuilder writerBuilder = EasyExcel.write(); // 輸出的文件對象,可以是File、路徑(字符串)或者OutputStream實例 writerBuilder.file(""); // 指定sheet,可以是數字序號sheetNo或者字符串sheetName,可以不設置,由下面提到的WriteSheet覆蓋 writerBuilder.sheet(""); // 文件的密碼 writerBuilder.password(""); // Excel文件格式,包括ExcelTypeEnum.XLSX和ExcelTypeEnum.XLS writerBuilder.excelType(ExcelTypeEnum.XLSX); // 是否自動關閉輸出流 writerBuilder.autoCloseStream(true); // 指定文件的標題行,可以是Class對象(結合@ExcelProperty注解使用),或者List實例 writerBuilder.head(Collections.singletonList(Collections.singletonList("head"))); // 構建ExcelWriter實例 ExcelWriter excelWriter = writerBuilder.build(); List data = new ArrayList(); // 構建輸出的sheet WriteSheet writeSheet = new WriteSheet(); writeSheet.setSheetName("target"); excelWriter.write(data, writeSheet); // 這一步一定要調用,否則輸出的文件有可能不完整 excelWriter.finish();
ExcelWriterBuilder
中還有很多樣式、行處理器、轉換器設置等方法,筆者覺得不常用,這里不做舉例,內容的樣式通常在輸出文件之后再次加工會更加容易操作。寫操作一般可以簡化如下:
List head = new ArrayList(); List data = new LinkedList(); EasyExcel.write("輸出文件絕對路徑") .head(head) .excelType(ExcelTypeEnum.XLSX) .sheet("target") .doWrite(data);
下面簡單介紹一下生產中用到的實用技巧。
使用EasyExcel
多線程讀建議在限定的前提條件下使用:
源文件已經被分割成多個小文件,并且每個小文件的標題行和列數一致。
機器內存要充足,因為并發讀取的結果最后需要合并成一個大的結果集,全部數據存放在內存中。
經常遇到外部反饋的多份文件需要緊急進行數據分析或者交叉校對,為了加快文件讀取,筆者通常使用這種方式批量讀取格式一致的Excel文件
一個簡單的例子如下:
@Slf4j public class EasyExcelConcurrentRead { static final int N_CPU = Runtime.getRuntime().availableProcessors(); public static void main(String[] args) throws Exception { // 假設I盤的temp目錄下有一堆同格式的Excel文件 String dir = "I:\\temp"; List mergeResult = Lists.newLinkedList(); ThreadPoolExecutor executor = new ThreadPoolExecutor(N_CPU, N_CPU * 2, 0, TimeUnit.SECONDS, new LinkedBlockingQueue(), new ThreadFactory() { private final AtomicInteger counter = new AtomicInteger(); @Override public Thread newThread(@NotNull Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); thread.setName("ExcelReadWorker-" + counter.getAndIncrement()); return thread; } }); Path dirPath = Paths.get(dir); if (Files.isDirectory(dirPath)) { List futures = Files.list(dirPath) .map(path -> path.toAbsolutePath().toString()) .filter(absolutePath -> absolutePath.endsWith(".xls") || absolutePath.endsWith(".xlsx")) .map(absolutePath -> executor.submit(new ReadTask(absolutePath))) .collect(Collectors.toList()); for (Future future : futures) { mergeResult.addAll(future.get()); } } log.info("讀取[{}]目錄下的文件成功,一共加載:{}行數據", dir, mergeResult.size()); // 其他業務邏輯..... } @RequiredArgsConstructor private static class ReadTask implements Callable { private final String location; @Override public List call() throws Exception { List data = Lists.newLinkedList(); EasyExcel.read(location).sheet() .registerReadListener(new AnalysisEventListener() { @Override public void invoke(Map row, AnalysisContext analysisContext) { data.add(row); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { log.info("讀取路徑[{}]文件成功,一共[{}]行", location, data.size()); } }).doRead(); return data; } } }
這里采用ThreadPoolExecutor#submit()
提交并發讀的任務,然后使用Future#get()
等待所有任務完成之后再合并最終的讀取結果。
注意,一般文件的寫操作不能并發執行,否則很大的概率會導致數據錯亂
多Sheet
寫,其實就是使用同一個ExcelWriter
實例,寫入多個WriteSheet
實例中,每個Sheet
的標題行可以通過WriteSheet
實例中的配置屬性進行覆蓋,代碼如下:
public class EasyExcelMultiSheetWrite { public static void main(String[] args) throws Exception { ExcelWriterBuilder writerBuilder = EasyExcel.write(); writerBuilder.excelType(ExcelTypeEnum.XLSX); writerBuilder.autoCloseStream(true); writerBuilder.file("I:\\temp\\temp.xlsx"); ExcelWriter excelWriter = writerBuilder.build(); WriteSheet firstSheet = new WriteSheet(); firstSheet.setSheetName("first"); firstSheet.setHead(Collections.singletonList(Collections.singletonList("第一個Sheet的Head"))); // 寫入第一個命名為first的Sheet excelWriter.write(Collections.singletonList(Collections.singletonList("第一個Sheet的數據")), firstSheet); WriteSheet secondSheet = new WriteSheet(); secondSheet.setSheetName("second"); secondSheet.setHead(Collections.singletonList(Collections.singletonList("第二個Sheet的Head"))); // 寫入第二個命名為second的Sheet excelWriter.write(Collections.singletonList(Collections.singletonList("第二個Sheet的數據")), secondSheet); excelWriter.finish(); } }
在一些數據量比較大的場景下,可以考慮分頁查詢和批量寫,其實就是分頁查詢原始數據 -> 數據聚合或者轉換 -> 寫目標數據 -> 下一頁查詢....
。其實數據量少的情況下,一次性全量查詢和全量寫也只是分頁查詢和批量寫的一個特例,因此可以把查詢、轉換和寫操作抽象成一個可復用的模板方法:
int batchSize = 定義每篇查詢的條數; OutputStream outputStream = 定義寫到何處; ExcelWriter writer = new ExcelWriterBuilder() .autoCloseStream(true) .file(outputStream) .excelType(ExcelTypeEnum.XLSX) .head(ExcelModel.class); for (;;){ List list = originModelRepository.分頁查詢(); if (list.isEmpty()){ writer.finish(); break; }else { list 轉換-> List excelModelList; writer.write(excelModelList); } }
下面的例子適用于Servlet容器,常見的如Tomcat,應用于spring-boot-starter-web
Excel
文件上傳跟普通文件上傳的操作差不多,然后使用EasyExcel
的ExcelReader
讀取請求對象MultipartHttpServletRequest
中文件部分抽象的InputStream
實例即可:
@PostMapping(path = "/upload") public ResponseEntity upload(MultipartHttpServletRequest request) throws Exception { Map fileMap = request.getFileMap(); for (Map.Entry part : fileMap.entrySet()) { InputStream inputStream = part.getValue().getInputStream(); Map head = new HashMap(); List data = new LinkedList(); EasyExcel.read(inputStream).sheet() .registerReadListener(new AnalysisEventListener() { @Override public void invokeHeadMap(Map headMap, AnalysisContext context) { head.putAll(headMap); } @Override public void invoke(Map row, AnalysisContext analysisContext) { data.add(row); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { log.info("讀取文件[{}]成功,一共:{}行......", part.getKey(), data.size()); } }).doRead(); // 其他業務邏輯 } return ResponseEntity.ok("success"); }
使用Postman
請求如下:
使用EasyExcel
進行Excel
文件導出也比較簡單,只需要把響應對象HttpServletResponse
中攜帶的OutputStream
對象附著到EasyExcel
的ExcelWriter
實例即可:
@GetMapping(path = "/download") public void download(HttpServletResponse response) throws Exception { // 這里文件名如果涉及中文一定要使用URL編碼,否則會亂碼 String fileName = URLEncoder.encode("文件名.xlsx", StandardCharsets.UTF_8.toString()); // 封裝標題行 List head = new ArrayList(); // 封裝數據 List data = new LinkedList(); response.setContentType("application/force-download"); response.setHeader("Content-Disposition", "attachment;filename=" + fileName); EasyExcel.write(response.getOutputStream()) .head(head) .autoCloseStream(true) .excelType(ExcelTypeEnum.XLSX) .sheet("Sheet名字") .doWrite(data); }
這里需要注意一下:
文件名如果包含中文,需要進行URL
編碼,否則一定會亂碼。
無論導入或者導出,如果數據量大比較耗時,使用了Nginx
的話記得調整Nginx
中的連接、讀寫超時時間的上限配置。
使用SpringBoot
需要調整spring.servlet.multipart.max-request-size
和spring.servlet.multipart.max-file-size
的配置值,避免上傳的文件過大出現異常。
以上就是“EasyExcel怎么引用”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。