驚了!7 行程式碼優雅地實現 Excel 檔案生成&下載功能
歡迎關注個人微信公眾號: 小哈學Java
個人網站: www.exception.site/essay/how-t…
目錄
-
一、前言
-
二、Apache poi、jxl 的缺陷
-
三、阿里出品的 EasyExcel,安利一波
-
四、EasyExcel 解決了什麼
-
五、快速上手
-
六、特殊場景支援
-
七、Web 下載示例程式碼
-
八、需要注意的點
-
九、總結
一、前言
關於匯出 Excel 檔案,可以說是大多數服務中都需要整合的功能。那麼,要如何優雅快速地( 偷懶地 )去實現這個功能呢?
你可能第一想法是:這還不簡單?用 Apache 開源框架 poi, 或者 jxl 都可以實現啊。面向百度程式設計,把程式碼模板 copy 下來,根據自己的業務再改改,能有多難?

嗯.. 的確不難,但是你的程式碼可能是下面這個熊樣子的:

上面這段程式碼看上去是不是又臭又長呢?今天,小哈將教您如何使用 7 行程式碼搞定 Excel 檔案生成功能!

二、Apache poi、jxl 的缺陷
在說如何實現之前,我們先來討論一下傳統 Excel 框架的不足!除了上面說的,Apache poi、jxl 都存在生成 excel 檔案不夠簡單優雅快速外,它們都還存在一個嚴重的問題,那就是 非常耗記憶體 , 嚴重時會導致記憶體溢位 。
POI 雖然目前來說,是 excel 解析框架中被使用最廣泛的,但這個框架並不完美。
為什麼這麼說呢?
開發者們大部分使用 POI,都是使用其 userModel 模式。而 userModel 的好處是上手容易使用簡單,隨便拷貝個程式碼跑一下,剩下就是寫業務轉換了,雖然轉換也要寫上百行程式碼,但是還是可控的。
然而 userModel 模式最大的問題是在於,對記憶體消耗非常大,一個幾兆的檔案解析甚至要用掉上百兆的記憶體。現實情況是,很多應用現在都在採用這種模式,之所以還正常在跑是因為併發不大,併發上來後,一定會OOM或者頻繁的 full gc。
三、阿里出品的 EasyExcel,安利一波
什麼是 EasyExcel? 見名知意,就是讓你操作 Excel 異常的酸爽。先來看下 EasyExcel GitHub 官方截圖:

截止目前為止已有 5519 Star, 官方對其的簡介是:
快速、簡單避免OOM的java處理Excel工具!
以下是官方介紹:

四、EasyExcel 解決了什麼
主要來說,有以下幾點:
- 傳統 Excel 框架,如 Apache poi、jxl 都存在記憶體溢位的問題;
- 傳統 excel 開源框架使用複雜、繁瑣;
- EasyExcel 底層還是使用的 poi, 但是做了很多優化,比如修復了併發情況下的一些 bug, 具體修復細節,可閱讀官方文件 github.com/alibaba/eas… ;

五、快速上手
5.1 新增依賴
<!--alibaba easyexcel--> <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>1.1.2-beta5</version> </dependency> 複製程式碼
5.2 七行程式碼搞定 Excel 生成

@Test public void writeExcel1() throws Exception { // 檔案輸出位置 OutputStream out = new FileOutputStream("/Users/a123123/Work/tmp_files/test.xlsx"); ExcelWriter writer = EasyExcelFactory.getWriter(out); // 寫僅有一個 Sheet 的 Excel 檔案, 此場景較為通用 Sheet sheet1 = new Sheet(1, 0, WriteModel.class); // 第一個 sheet 名稱 sheet1.setSheetName("第一個sheet"); // 寫資料到 Writer 上下文中 // 入參1: 建立要寫入的模型資料 // 入參2: 要寫入的目標 sheet writer.write(createModelList(), sheet1); // 將上下文中的最終 outputStream 寫入到指定檔案中 writer.finish(); // 關閉流 out.close(); } 複製程式碼
上面這段示例程式碼中,有兩個點很重要,小哈已經重點標註標:
- ① :WriteModel 這個物件就是要寫入 Excel 的資料模型物件,**等等,你這好像不行吧?表頭 head,以及每個單元格內的資料順序都沒指定,能達到想要的效果麼?別急,後面會討論這塊!
- ② :建立 需要寫入的資料集 ,當然了,正常業務中,這塊都是從資料庫中查詢出來的。
PS: 如果說寫入的資料量很大,需要做分片查詢再寫入的處理,否則可能會 OOM(Out of Memory).
回過頭來,我們來看看 WriteModel
這個物件內部到底有什麼么蛾子!

/** * @author 微信公眾號: 小哈學Java * @Site: www.exception.site * @date 2019/5/9 * @time 下午2:07 * @discription 寫入Excel模型物件 **/ @Data @NoArgsConstructor @AllArgsConstructor @Builder public class WriteModel extends BaseRowModel { @ExcelProperty(value = "姓名", index = 0) private String name; @ExcelProperty(value = "密碼", index = 1) private String password; @ExcelProperty(value = "年齡", index = 2) private Integer age; } 複製程式碼
ExayExcel 提供註解的方式, 來方便的定義 Excel 需要的資料模型:
- ① :首先,定義的寫入模型必須要繼承自
BaseRowModel.java
; - ② :通過
@ExcelProperty
註解來指定每個欄位的 列名稱 ,以及 下標位置 ;
同時,上面定義的 createModelList()
方法也很簡單,通過迴圈,建立一個寫入模型的 List 集合:

廢話不多說,這個快速接入的案例也介紹的差不多了,跑一跑單元測試看下實際效果:

怎麼樣,效果還是挺棒棒的!

六、特殊場景支援
在實際的業務中,我們還會有一些特需的需求,比如說下面這些。
6.1 動態生成 Excel 內容
上面的例子是基於註解的,也就是說表頭 head, 以及內容都是寫死的,換句話說,我定義好了一個數據模型,那麼,生成的 Excel 檔案也就是隻能遵循這種模型來了,但是,實際業務中可能會存在動態變化的需求,要怎麼做呢?

@Test public void writeExcel2() throws Exception { // 檔案輸出位置 OutputStream out = new FileOutputStream("/Users/a123123/Work/tmp_files/test2.xlsx"); ExcelWriter writer = EasyExcelFactory.getWriter(out); // 動態新增表頭,適用一些表頭動態變化的場景 Sheet sheet1 = new Sheet(1, 0); sheet1.setSheetName("第一個sheet"); // 建立一個表格,用於 Sheet 中使用 Table table1 = new Table(1); // 無註解的模式,動態新增表頭 table1.setHead(DataUtil.createTestListStringHead()); // 寫資料 writer.write1(createDynamicModelList(), sheet1, table1); // 將上下文中的最終 outputStream 寫入到指定檔案中 writer.finish(); // 關閉流 out.close(); } 複製程式碼
- ① :無註解模式,動態新增表頭,也可自由組合複雜表頭,程式碼如下:

public static List<List<String>> createTestListStringHead(){ // 模型上沒有註解,表頭資料動態傳入 List<List<String>> head = new ArrayList<List<String>>(); List<String> headCoulumn1 = new ArrayList<String>(); List<String> headCoulumn2 = new ArrayList<String>(); List<String> headCoulumn3 = new ArrayList<String>(); List<String> headCoulumn4 = new ArrayList<String>(); List<String> headCoulumn5 = new ArrayList<String>(); headCoulumn1.add("第一列");headCoulumn1.add("第一列");headCoulumn1.add("第一列"); headCoulumn2.add("第一列");headCoulumn2.add("第一列");headCoulumn2.add("第一列"); headCoulumn3.add("第二列");headCoulumn3.add("第二列");headCoulumn3.add("第二列"); headCoulumn4.add("第三列");headCoulumn4.add("第三列2");headCoulumn4.add("第三列2"); headCoulumn5.add("第一列");headCoulumn5.add("第3列");headCoulumn5.add("第4列"); head.add(headCoulumn1); head.add(headCoulumn2); head.add(headCoulumn3); head.add(headCoulumn4); head.add(headCoulumn5); return head; } 複製程式碼
- ② :建立動態資料,注意這裡的資料型別是
Object
:

跑一下單元測試,看下效果:

6.2 自定義表頭以及內容樣式
我想自定義表頭,內容樣式,咋辦?

我們複用了上面的示例程式碼,並額外添加了設定自定義表格樣式的程式碼, createTableStytle()
具體內容如下:

public static TableStyle createTableStyle() { TableStyle tableStyle = new TableStyle(); // 設定表頭樣式 Font headFont = new Font(); // 字型是否加粗 headFont.setBold(true); // 字型大小 headFont.setFontHeightInPoints((short)12); // 字型 headFont.setFontName("楷體"); tableStyle.setTableHeadFont(headFont); // 背景色 tableStyle.setTableHeadBackGroundColor(IndexedColors.BLUE); // 設定表格主體樣式 Font contentFont = new Font(); contentFont.setBold(true); contentFont.setFontHeightInPoints((short)12); contentFont.setFontName("黑體"); tableStyle.setTableContentFont(contentFont); tableStyle.setTableContentBackGroundColor(IndexedColors.GREEN); return tableStyle; } 複製程式碼
我們可以通過 TableStyle
這個類來設定表頭、表格主題的樣式。
6.3 合併單元格
我們可以通過 merge()
方法來合併單元格:

注意下標是從 0 開始的,也就是說合並了第六行到第七行,其中的第一列到第五列,跑下程式碼,看下效果:

6.4 自定義處理
對於更復雜的處理,EasyExcel 預留了 WriterHandler
介面來,允許你自定義處理程式碼:

介面中定義了三個方法:
sheet() row() cell()
我們實現了該介面後,編寫自定義邏輯處理程式碼,然後呼叫 getWriterWithTempAndHandler()
靜態方法獲取 ExcelWriter
物件時,傳入 WriterHandler
的實現類即可。

比如下面的示例程式碼:
ExcelWriter writer = EasyExcelFactory.getWriterWithTempAndHandler(null, out, ExcelTypeEnum.XLSX, true, new MyWriterHandler()); 複製程式碼
七、Web 下載示例程式碼
public class Down { @GetMapping("/a.htm") public void cooperation(HttpServletRequest request, HttpServletResponse response) { ServletOutputStream out = response.getOutputStream(); ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX, true); String fileName = new String(("UserInfo " + new SimpleDateFormat("yyyy-MM-dd").format(new Date())) .getBytes(), "UTF-8"); Sheet sheet1 = new Sheet(1, 0); sheet1.setSheetName("第一個sheet"); writer.write0(getListString(), sheet1); writer.finish(); response.setContentType("multipart/form-data"); response.setCharacterEncoding("utf-8"); response.setHeader("Content-disposition", "attachment;filename="+fileName+".xlsx"); out.flush(); } } 複製程式碼
八、需要注意的點
8.1 寫入大資料時,需分片
比如說,我們需要從資料庫中查詢出資料量較大時,我們需要在業務層做分片處理,也就是,我們需要分多次查詢,再寫入,防止記憶體溢位 OOM.
8.2 Excel 最大行數問題
Excel 03, 07 版本均有行數、列數的限制:
版本 | 最大行 | 最大列 |
---|---|---|
Excel 2003 | 65536 | 256 |
Excel 2007 | 1048576 | 16384 |
csv 由於是文字檔案,實際上沒有最大行數的限制,但是用 Excel 客戶端開啟還是多了不顯示。
也就是說,如果你想寫入更多的行數是不行的,強行這麼做,程式會報類似如下異常
Invalid row number (1048576) outside allowable range (0..1048575) 複製程式碼
如何解決呢?
- 分多個 Excel 檔案寫入;
- 同一個 Excel 檔案,分多個 Sheet 寫入;
九、總結
小哈今天主要給小夥伴介紹了 EasyExcel, 為什麼要使用它,以及演示了相關示例程式碼。當然了,EasyExcel 除了寫 Excel 檔案外,它還有快速讀取 Excel 的功能,由於本文主要介紹的是:如何優雅地實現 Excel 檔案生成,所以就沒有介紹了,有興趣的小夥伴們,也可以去 GitHub 官網 去去檢視相關文件。
最後,祝您看完本文後有所收穫,下期見!
十、GitHub 原始碼地址
十一、Ref
免費分享 | 面試&學習福利資源
最近在網上發現一個不錯的 PDF 資源《Java 核心知識&面試.pdf》分享給大家,不光是面試,學習,你都值得擁有!!!
獲取方式: 關注公眾號: 小哈學Java , 後臺回覆 資源 ,既可 免費無套路獲取資源連結 ,下面是目錄以及部分截圖:






重要的事情說兩遍,關注公眾號: 小哈學Java , 後臺回覆 資源 ,既可 免費無套路獲取資源連結 !!!