EasyExcel:阿里開源的高效 Excel 處理工具,輕松解決 POI 內存溢出問題

在日常開發中,Excel 文件的導入導出是非常常見的需求。無論是數據批量導入、報表生成還是數據備份,我們都離不開對 Excel 的操作。但傳統的 POI 框架在處理大數據量 Excel 時,常常會遇到內存溢出的問題,讓開發者頭疼不已。

今天給大家介紹一款阿里開源的 Excel 處理工具 ——EasyExcel,它以低內存占用為核心優勢,完美解決了大數據量 Excel 處理的痛點。

什么是 EasyExcel?

EasyExcel 是阿里巴巴開源的一個基于 Java 的 Excel 處理工具,它重寫了 POI 對 Excel 的解析方式,通過事件驅動模式增量解析的方式,在讀取 Excel 時不會將整個文件加載到內存中,而是逐行解析,大大降低了內存占用。

項目地址:https://github.com/alibaba/easyexcel

EasyExcel 的核心優勢

  1. 內存占用極低

    • 傳統 POI 解析 Excel 時,會將整個文檔加載到內存,對于百萬級數據的 Excel,很容易導致 OOM
    • EasyExcel 采用逐行解析模式,內存占用可以控制在 KB 級別
  2. API 簡潔易用

    • 封裝了復雜的 Excel 解析邏輯,提供簡單直觀的 API
    • 注解驅動,通過注解即可完成 Excel 與實體類的映射
  3. 功能完善

    • 支持 Excel 的讀寫操作
    • 支持 xls、xlsx 等多種格式
    • 支持復雜表頭、合并單元格等復雜場景
    • 支持大數據量的導入導出
  4. 擴展性強

    • 提供豐富的監聽器接口,可以自定義處理邏輯
    • 支持自定義轉換器,處理特殊格式數據

快速入門:EasyExcel 基本使用

1. 引入依賴

首先在項目中引入 EasyExcel 的 Maven 依賴

<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.0</version>
</dependency>

2. 定義實體類

通過注解定義 Excel 與實體類的映射關系

@Data
public class UserData {// index表示列的索引,value表示列名@ExcelProperty(index = 0, value = "姓名")private String name;@ExcelProperty(index = 1, value = "年齡")private Integer age;@ExcelProperty(index = 2, value = "郵箱")private String email;// 日期格式化@ExcelProperty(index = 3, value = "注冊時間")@DateTimeFormat("yyyy-MM-dd HH:mm:ss")private Date registerTime;
}

3. 寫入 Excel 文件

public class ExcelWriteDemo {public static void main(String[] args) {// 準備數據List<UserData> dataList = new ArrayList<>();for (int i = 0; i < 10; i++) {UserData data = new UserData();data.setName("用戶" + i);data.setAge(20 + i);data.setEmail("user" + i + "@example.com");data.setRegisterTime(new Date());dataList.add(data);}// 寫入文件String fileName = "D:/user_data.xlsx";EasyExcel.write(fileName, UserData.class).sheet("用戶列表")  // 指定工作表名稱.doWrite(dataList); // 寫入數據}
}

4. 讀取 Excel 文件

讀取 Excel 需要定義一個監聽器:

// 自定義監聽器
public class UserDataListener extends AnalysisEventListener<UserData> {// 每解析一行數據就會調用一次@Overridepublic void invoke(UserData data, AnalysisContext context) {System.out.println("解析到數據:" + data);// 可以在這里處理數據,如存入數據庫}// 所有數據解析完成后調用@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {System.out.println("所有數據解析完成");}
}// 讀取Excel
public class ExcelReadDemo {public static void main(String[] args) {String fileName = "D:/user_data.xlsx";EasyExcel.read(fileName, UserData.class, new UserDataListener()).sheet()  // 讀取第一個工作表.doRead(); // 開始讀取}
}

Web 場景下的 Excel 導出

在 Web 項目中,我們經常需要實現 Excel 導出功能,讓用戶可以直接下載文件:

@RequestMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {// 準備數據List<UserData> dataList = getUserDataList();// 設置響應頭response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("UTF-8");String fileName = URLEncoder.encode("用戶數據", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");// 寫入響應流EasyExcel.write(response.getOutputStream(), UserData.class).sheet("用戶列表").doWrite(dataList);
}

高級特性

  1. 大數據量處理

EasyExcel 專門為大數據量場景設計,即使處理百萬級數據也不會出現內存問題:

// 讀取大數據量Excel
EasyExcel.read(fileName, UserData.class, new UserDataListener()).batchRead(1000)  // 批量讀取,每1000條處理一次.sheet().doRead();
  1. 復雜表頭處理

支持多級表頭的導入導出:

// 定義復雜表頭
List<List<String>> head = new ArrayList<>();
head.add(Arrays.asList("用戶信息", "姓名"));
head.add(Arrays.asList("用戶信息", "年齡"));
head.add(Arrays.asList("聯系信息", "郵箱"));// 寫入復雜表頭
EasyExcel.write(fileName).head(head).sheet("復雜表頭示例").doWrite(dataList);
  1. 數據轉換與格式化

通過自定義轉換器處理特殊格式的數據:

// 自定義轉換器
public class CustomConverter implements Converter<LocalDateTime> {@Overridepublic Class<LocalDateTime> supportJavaTypeKey() {return LocalDateTime.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}// 讀取時轉換@Overridepublic LocalDateTime convertToJavaData(ReadConverterContext<?> context) {return LocalDateTime.parse(context.getReadCellData().getStringValue(), DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss"));}// 寫入時轉換@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<LocalDateTime> context) {return new WriteCellData<>(context.getValue().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss")));}
}

一個完整的ExcelUtils類


public class ExcelUtils {/***   讀取前端上傳的excel文件*/public static <T> void readAnalysis(MultipartFile file, Class<T> head, ExcelFinishCallBack<T> callBack) {try {EasyExcel.read(file.getInputStream(), head, new ExcelDataListener<>(callBack)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}}/*** 讀取本地excel文件** @param <T>      數據類型* @param file     excel文件* @param head     列名* @param callBack 回調 導入時傳入定義好的回調接口,excel數據解析完畢之后監聽器將數據傳入回調函數*                 這樣調用工具類時可以通過回調函數獲取導入的數據,如果數據量過大可根據實際情況進行分配入庫*/public static <T> void readAnalysis(File file, Class<T> head, ExcelFinishCallBack<T> callBack) {try {EasyExcel.read(new FileInputStream(file), head, new ExcelDataListener<>(callBack)).sheet().doRead();} catch (IOException e) {e.printStackTrace();}}/*** 讀取excel文件 同步** @param <T>   數據類型* @param file  文件* @param clazz 模板類* @return java.util.List*/public static <T> List<T> readSync(File file, Class<T> clazz) {return readSync(file, clazz, 1, 0, ExcelTypeEnum.XLSX);}/*** 讀取excel文件 同步** @param <T>       數據類型* @param file      文件* @param clazz     模板類* @param rowNum    數據開始行 1* @param sheetNo   第幾張表* @param excelType 數據表格式類型* @return java.util.List list*/public static <T> List<T> readSync(File file, Class<T> clazz, Integer rowNum, Integer sheetNo, ExcelTypeEnum excelType) {return EasyExcel.read(file).headRowNumber(rowNum).excelType(excelType).head(clazz).sheet(sheetNo).doReadSync();}/*** 導出數據到文件** @param <T>  數據類型* @param head 類名* @param file 導入到本地文件* @param data 數據*/public static <T> void excelExport(Class<T> head, File file, List<T> data) {excelExport(head, file, "sheet1", data);}/*** 導出數據到文件** @param <T>       寫入格式* @param head      類名* @param file      寫入到文件* @param sheetName sheet名稱* @param data      數據列表*/public static <T> void excelExport(Class<T> head, File file, String sheetName, List<T> data) {try {EasyExcel.write(file, head).sheet(sheetName).doWrite(data);} catch (Exception e) {throw new RuntimeException(e);}}/*** 導出數據到web* 文件下載(失敗了會返回一個有部分數據的Excel)** @param head      類名* @param excelName excel名字* @param sheetName sheet名稱* @param data      數據*                  數據導出到web響應*/public static <T> void excelExport(Class<T> head, String excelName, String sheetName, List<T> data) {try {HttpServletResponse response = getExportResponse(excelName);EasyExcel.write(response.getOutputStream(), head).sheet(StringUtils.isBlank(sheetName) ? "sheet1" : sheetName).doWrite(data);} catch (IOException e) {throw new RuntimeException(e);}}/*** 導出數據到web* 文件下載(失敗了會返回一個有部分數據的Excel)** @param head      類名* @param excelName excel名字* @param sheetName sheet名稱* @param data      數據*/public static <T> void excelExport(List<List<String>> head, String excelName, String sheetName, List<T> data) {try {HttpServletResponse response = getExportResponse(excelName);EasyExcel.write(response.getOutputStream()).head(head).sheet(StringUtils.isBlank(sheetName) ? "sheet1" : sheetName).doWrite(data);} catch (IOException e) {throw new RuntimeException(e);}}/*設置編碼格式,允許前端訪問文件名進行跨域審核
*/private static HttpServletResponse getExportResponse(String excelName) {//獲得當前HTTP響應對象HttpServletResponse response = HttpContextUtils.getHttpServletResponse();//告訴瀏覽器返回的是excel文件response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");//允許前端讀取文件名 filename:指定下載時顯示的文件名response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");//設置編碼格式response.setCharacterEncoding("UTF-8");//將文件名進行編碼String fileName = URLUtil.encode(excelName, StandardCharsets.UTF_8);//允許前段JavaScript訪問Content-Disposition頭 獲取文件名response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");return response;}/*** 解析字典數據到字段上* 比如 T中有 genderLabel字段 為男 需要給 gender 字段自動設置為0** @param dataList 需要被反向解析的數據*/@SneakyThrowspublic static <T extends TransPojo> void parseDict(List<T> dataList) {//沒有數據就不需要初始化if (CollectionUtil.isEmpty(dataList)) {return;}Class<? extends TransPojo> clazz = dataList.get(0).getClass();//拿到所有需要反向翻譯的字段  注解帶Trans注解的字段List<Field> fields = ReflectUtils.getAnnotationField(clazz, Trans.class);//過濾出類型為字典TransType.DICTIONARY的字段fields = fields.stream().filter(field -> TransType.DICTIONARY.equals(field.getAnnotation(Trans.class).type())).collect(Collectors.toList());//從spring容器中獲取字典轉換服務DictionaryTransService dictionaryTransService = SpringUtil.getBean(DictionaryTransService.class);//反射設置值for (T data : dataList) {//獲取關聯字段的值for (Field field : fields) {//從字典中服務中獲取映射值Trans trans = field.getAnnotation(Trans.class);// key不能為空并且ref不為空的才自動處理if (StrUtil.isAllNotBlank(trans.key(), trans.ref())) {
//                    根據字段名獲取對應的Field對象  類似于user類中的gender字段Field ref = ReflectUtils.getDeclaredField(clazz, trans.ref());//打開訪問私有屬性的開關ref.setAccessible(true);// 獲取字典映射值String value = dictionaryTransService.getDictionaryTransMap().get(trans.key() + "_" + ref.get(data));if (StringUtils.isBlank(value)) {continue;}// 一般目標字段是int或者string字段 后面有添加單獨抽離方法if (Integer.class.equals(field.getType())) {field.setAccessible(true);field.set(data, ConverterUtils.toInteger(value));} else {field.setAccessible(true);field.set(data, ConverterUtils.toString(value));}}}}}}

總結

EasyExcel 作為一款優秀的 Excel 處理工具,憑借其低內存占用、簡單易用的特點,已經成為 Java 開發中處理 Excel 的首選框架。無論是簡單的 Excel 導入導出,還是復雜的大數據量處理場景,EasyExcel 都能輕松應對。

如果你還在為 POI 的內存問題煩惱,不妨試試 EasyExcel,相信它會給你帶來驚喜!

歡迎在評論區分享你使用 EasyExcel 的經驗和技巧~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/98097.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/98097.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/98097.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

軟件啟動時加配置文件 vs 不加配置文件

一、基本概念不加配置文件啟動直接執行啟動命令&#xff0c;使用軟件自帶的默認參數。方便、快速&#xff0c;適合測試環境。缺點&#xff1a;靈活性差、配置不可控、不安全。redis-server zookeeper-server-start.sh kafka-server-start.sh指定配置文件啟動啟動時加載外部配置…

[ubuntu][C++]onnxruntime安裝cpu版本后測試代碼

下載官方預編譯包后&#xff0c;怎么用呢。可以參考這個源碼跑測試環境&#xff1a;ubuntu22.04onnxruntime1.18.0測試代碼&#xff1a;CMakeLists.txtcmake_minimum_required(VERSION 3.12) project(onnx_test)# 設置C標準 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD…

棧:有效的括號

題目描述&#xff1a;給定一個只包含‘[’,{,(,),},]的字符串&#xff0c;判斷該字符串是否括號有效。 括號有效的要求是&#xff1a; 每個左括號都有對應的右括號。每個右括號都有對應的左括號。左括號必須以正確的順序閉合。 示例 1&#xff1a; 輸入&#xff1a;s "…

微前端架構:解構前端巨石應用的藝術

在數字化轉型浪潮中&#xff0c;企業級前端應用正變得日益復雜。微前端架構作為一種創新的解決方案&#xff0c;正在重新定義大型前端應用的構建方式&#xff0c;使多個團隊能夠獨立開發和部署功能模塊一、微前端架構的核心價值理念微前端架構的本質是將后端微服務理念擴展到前…

《LangChain從入門到精通》系統學習教材大綱

&#x1f4da; 《LangChain從入門到精通》系統學習教材大綱 目標&#xff1a;幫助你系統掌握LangChain理論與實踐&#xff0c;成為具備獨立開發能力的AI應用開發者。 學習周期建議&#xff1a;8~12周&#xff08;每天2~3小時&#xff09;&#xff0c;配合項目實戰可加速成長。 …

Redis 的相關文件作用

Java 項目中使用 Redis 的相關文件&#xff0c;下面我來逐一解釋它們的作用&#xff1a;1. RedisDemoApplicationTests.java 作用&#xff1a;這是 Spring Boot 項目的測試類。用途&#xff1a; 通常用于寫單元測試或集成測試。測試 Redis 功能是否正常&#xff0c;比如連接、讀…

【React】性能提升方案:Reat.memo, useMemo,useCallback用法詳解

前言&#xff1a;Reat.memo, useMemo,useCallback是React中用于性能優化的三個核心API&#xff0c;它們分別針對組件渲染&#xff0c;計算緩存和函數引用進行優化。一、React.memo作用&#xff1a;緩存組件&#xff0c;當父組件重新渲染時&#xff0c;若子組件的props未變化&am…

Alibaba Cloud Linux 3 安裝Docker

Alibaba Cloud Linux 3 基于 Red Hat Enterprise Linux (RHEL) 兼容內核&#xff0c;安裝 Docker 的步驟與 RHEL/CentOS 系列類似&#xff0c;以下是具體操作&#xff1a; 1. 卸載舊版本&#xff08;如有&#xff09; sudo dnf remove docker docker-client docker-client-la…

每日一練001.pm

題目詳情&#xff1a; P5705 【深基2.例7】數字反轉 - 洛谷 題目描述 輸入一個不小于 100 且小于 1000&#xff0c;同時包括小數點后一位的一個浮點數&#xff0c; 例如 123.4 &#xff0c;要求把這個數字翻轉過來&#xff0c;變成 4.321 并輸出。 #include<iostream&g…

AI智能優化SEO關鍵詞策略實戰

本文聚焦AI如何智能優化SEO關鍵詞策略&#xff0c;通過實戰案例分享高效技巧&#xff0c;幫助提升網站搜索排名和流量轉化效果。內容涵蓋AI革新關鍵詞策略的原理、智能優化技巧的實際應用、高效關鍵詞布局方法、避免常見錯誤的實戰指南&#xff0c;以及綜合策略推動排名飛躍的路…

360° 拖動旋轉的角度計算原理

360 拖動旋轉的角度計算原理 簡化的 正方形 div demo 專注講清楚「點擊 / 拖動如何計算角度」這個原理&#xff0c;沒有精美 UI哦 中間標注中心點鼠標點擊或拖動時&#xff0c;計算當前位置相對于中心的角度在頁面上實時顯示角度代碼示例&#xff08;原生 HTML JS&#xff09;…

五分鐘XML速成

原文鏈接&#xff1a; XML - Dive Into Python 3 深入探討 本書幾乎所有章節都圍繞一段示例代碼展開&#xff0c;但 XML 并非關于代碼&#xff0c;而是關于數據。 XML 的一個常見用途是 “聚合提要”&#xff08;syndication feeds&#xff09;&#xff0c;用于列出博客、論壇…

如何直接訪問docker容器中的端口服務而不需要改端口映射

查看docker容器對于宿主服務器的ip地址 docker inspect -f {{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}} $容器名 # 替換$容器名 為自己的啟動docker內的服務&#xff0c;監聽端口是否用信息 curl http://172.17.0.2:90有信息就可以直接通過該ip訪問docker容器端口…

《Istio故障溯源:從流量劫持異常到服務網格的底層博弈》

服務網格常被企業視為微服務通信復雜性的“終極方案”。不少團隊在部署Istio時,往往滿足于“控制面啟動、Sidecar注入成功”的表層驗證,卻忽視了底層機制與業務場景的深度適配—這種“重部署輕調優”的心態,往往為后續的生產故障埋下隱患。某大型金融機構的核心交易中臺在接…

第24節:3D音頻與空間音效實現

第24節&#xff1a;3D音頻與空間音效實現 概述 3D音頻是構建沉浸式體驗的關鍵組件&#xff0c;它通過模擬真實世界中的聲音傳播特性&#xff0c;為用戶提供空間感知和方向感。本節將深入探討Web Audio API與Three.js的集成&#xff0c;涵蓋空間音效原理、音頻可視化、多聲道處理…

一步搞清楚本地客戶端和全局服務器是如何更新模型的

我們可以把它想象成一個 “老師”和“學生” 協作學習的過程。全局服務器 “老師”本地客戶端 “學生”整個模型更新的過程遵循一個核心原則&#xff1a;“數據不動&#xff0c;模型動”。原始數據永遠留在本地客戶端&#xff0c;只有模型的參數&#xff08;即模型的“知識”…

跨平臺超低延遲RTSP播放器技術設計探究

摘要 RTSP 播放在實驗室里“跑起來”并不難&#xff0c;難的是在真實場景中做到 超低延遲、跨平臺、高穩定&#xff0c;并長期可靠運行。大牛直播SDK&#xff08;SmartMediaKit&#xff09;的全自研跨平臺 RTSP 播放棧&#xff0c;正是把這些工程難題轉化為可用、可控、可交付的…

知識點匯集——web(三)

1.index.php 的備份文件名通常為index.php.bak 2.PHP2是服務器端腳本語言&#xff0c;主要用于處理和生成網頁的內容&#xff0c;當用戶訪問一個網站時&#xff0c;PHP腳本會在服務器上執行&#xff0c;生成動態的HTML頁面&#xff0c;然后將頁面發送給用戶的瀏覽器進行顯示。p…

變頻器【簡易PLC】功能中的時間問題

一、變頻器的簡易PLC功能簡易PLC功能是將提前設置好的多端速頻率&#xff0c;進行自動運行&#xff0c;類似于PLC程序中的CASE指令一樣&#xff0c;我們需要提前設置好幾段頻率&#xff0c;該頻率所維持的時間&#xff0c;以及加減速時間&#xff0c;按下啟動后&#xff0c;變頻…

Swift 解題:LeetCode 372 超級次方(Super Pow)

文章目錄摘要描述題解答案題解代碼分析代碼解析示例測試及結果時間復雜度空間復雜度總結摘要 在算法題里&#xff0c;有一些問題看似“簡單”&#xff0c;比如算一個冪次方&#xff0c;但一旦放大規模就完全不同了。LeetCode 372 超級次方就是這樣的題目。普通的冪運算沒什么難…