天天看點

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

歡迎關注部落客公衆号「小哈學Java」, 專注于分享Java領域幹貨文章, 關注回複「資源」, 免費領取全網最熱的Java架構師學習PDF, 轉載請注明出處 https://www.exception.site/essay/how-to-create-excel-by-seven-line-code

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

一、前言

關于導出 Excel 檔案,可以說是大多數服務中都需要內建的功能。那麼,要如何優雅快速地(偷懶地)去實作這個功能呢?

你可能第一想法是:這還不簡單?用 Apache 開源架構 poi, 或者 jxl 都可以實作啊。面向百度程式設計,把代碼模闆 copy 下來,根據自己的業務再改改,能有多難?

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

二、Apache poi、jxl 的缺陷

在說如何實作之前,我們先來讨論一下傳統 Excel 架構的不足!除了上面說的,Apache poi、jxl 都存在生成 excel 檔案不夠簡單優雅快速外,它們都還存在一個嚴重的問題,那就是非常耗記憶體,嚴重時會導緻記憶體溢出。

POI 雖然目前來說,是 excel 解析架構中被使用最廣泛的,但這個架構并不完美。

為什麼這麼說呢?

開發者們大部分使用 POI,都是使用其 userModel 模式。而 userModel 的好處是上手容易使用簡單,随便拷貝個代碼跑一下,剩下就是寫業務轉換了,雖然轉換也要寫上百行代碼,但是還是可控的。

然而 userModel 模式最大的問題是在于,對記憶體消耗非常大,一個幾兆的檔案解析甚至要用掉上百兆的記憶體。現實情況是,很多應用現在都在采用這種模式,之是以還正常在跑是因為并發不大,并發上來後,一定會OOM或者頻繁的 full gc。

三、阿裡出品的 EasyExcel,安利一波

什麼是 EasyExcel? 見名知意,就是讓你操作 Excel 異常的酸爽。先來看下 EasyExcel GitHub 官方截圖:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

截止目前為止已有 5519 Star, 官方對其的簡介是:

快速、簡單避免OOM的java處理Excel工具!

以下是官方介紹:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

四、EasyExcel 解決了什麼

主要來說,有以下幾點:

  • 傳統 Excel 架構,如 Apache poi、jxl 都存在記憶體溢出的問題;
  • 傳統 excel 開源架構使用複雜、繁瑣;
  • EasyExcel 底層還是使用的 poi, 但是做了很多優化,比如修複了并發情況下的一些 bug, 具體修複細節,可閱讀官方文檔 https://github.com/alibaba/easyexcel;
Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&下載下傳功能

五、快速上手

5.1 添加依賴

<!--alibaba easyexcel-->
<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>1.1.2-beta5</version>
</dependency>
           

5.2 七行代碼搞定 Excel 生成

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能
@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

 這個對象内部到底有什麼幺蛾子!

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能
/**
 * @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 集合:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

六、特殊場景支援

在實際的業務中,我們還會有一些特需的需求,比如說下面這些。

6.1 動态生成 Excel 内容

上面的例子是基于注解的,也就是說表頭 head, 以及内容都是寫死的,換句話說,我定義好了一個資料模型,那麼,生成的 Excel 檔案也就是隻能遵循這種模型來了,但是,實際業務中可能會存在動态變化的需求,要怎麼做呢?

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能
@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();
}
           
  • ①:無注解模式,動态添加表頭,也可自由組合複雜表頭,代碼如下:
Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能
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

    :
Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

6.2 自定義表頭以及内容樣式

我想自定義表頭,内容樣式,咋辦?

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

我們複用了上面的示例代碼,并額外添加了設定自定義表格樣式的代碼, 

createTableStytle()

 具體内容如下:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能
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()

 方法來合并單元格:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

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

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

6.4 自定義處理

對于更複雜的處理,EasyExcel 預留了 

WriterHandler

 接口來,允許你自定義處理代碼:

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

接口中定義了三個方法:

  • sheet()

    : 在建立每個 sheet 後自定義業務邏輯處理;
  • row()

    : 在建立每個 row 後自定義業務邏輯處理;
  • cell()

    : 在建立每個 cell 後自定義業務邏輯處理;

我們實作了該接口後,編寫自定義邏輯處理代碼,然後調用 

getWriterWithTempAndHandler()

 靜态方法擷取 

ExcelWriter

 對象時,傳入 

WriterHandler

 的實作類即可。

Java實作Excel檔案生成和下載下傳功能7 行代碼優雅地實作 Excel 檔案生成&amp;下載下傳功能

比如下面的示例代碼:

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) {
	String fileName = new String(("UserInfo " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .getBytes(), "UTF-8");
        ServletOutputStream out = response.getOutputStream();
         response.setContentType("multipart/form-data");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename="+fileName+".xlsx");
        ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX, true);
        Sheet sheet1 = new Sheet(1, 0);
        sheet1.setSheetName("第一個sheet");
        writer.write0(getListString(), sheet1);
        writer.finish();
        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)
           

如何解決呢?

  1. 分多個 Excel 檔案寫入;
  2. 同一個 Excel 檔案,分多個 Sheet 寫入;

九、總結

小哈今天主要給小夥伴介紹了 EasyExcel, 為什麼要使用它,以及示範了相關示例代碼。當然了,EasyExcel 除了寫 Excel 檔案外,它還有快速讀取 Excel 的功能,由于本文主要介紹的是:如何優雅地實作 Excel 檔案生成,是以就沒有介紹了,有興趣的小夥伴們,也可以去 GitHub 官網去去檢視相關文檔。

最後,祝您看完本文後有所收獲,下期見!

十、GitHub 源碼位址

https://github.com/weiwosuoai/spring-boot-tutorial/tree/master/spring-boot-excel

十一、Ref

https://github.com/alibaba/easyexcel