【EasyExcel】十分鐘入門 EasyExcel

POI 的 API 較複雜,而且低版本有OOM的風險。
快節奏的互聯網時代,使開發效率成了我們技術人追求。
這裏帶大家十分鐘入門 alibaba/easyexcel

alibaba/easyexcel github

alibaba/easyexcel doc

Writer

  • head 方法支持傳入參數的格式

    • public ExcelWriterBuilder head(Class clazz)
    • public ExcelWriterBuilder head(List<List<String>> head)
  • doWrite 方法支持傳入參數的格式

    • public void doWrite(List<Class> data)
    • public void doWrite(List<List<Object>> data)
      在這裏插入圖片描述

使用對象寫

   /**
     * 最簡單的寫 使用定義的對象寫
     * <p>1. 創建excel對應的實體對象 參照{@link WriteData}
     * <p>2. 獲取寫入的數據 List<Class>
     * <p>3. 直接寫即可
     */
    @Test
    public void simpleWrite() {
        // 這裏 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字爲模板 然後文件流會自動關閉
        // 如果這裏想使用03 則 傳入excelType參數即可
        EasyExcel.write("./test.xlsx", WriteData.class)
                .excelType(ExcelTypeEnum.XLSX)
                .sheet("demoData")
                .doWrite(data());
    }

WriteData.java

@Data
@Builder
@HeadRowHeight(25)
@ContentRowHeight(20)
@ColumnWidth(30)
public class WriteData {

    @ExcelProperty(value = "字符串標題", converter = CustomTitleConverter.class)
    private String string;
    
    @DateTimeFormat("yyyy年MM月dd日 HH時mm分ss秒")
    @ExcelProperty(value = "日期標題")
    private Date date;
    
    @ExcelProperty("數字標題")
    private Double doubleData;
    
    @ExcelProperty(value = "禁用標誌", converter = CustomDisabledConverter.class)
    private Boolean disabled;
    
    /**
     * 忽略這個字段
     */
    @ExcelIgnore
    private String ignore;
    
}

使用 list 寫

    /**
     * 使用list寫
     * <p>1. 定義List<List<String>>結構的表頭
     * <p>2. 定義List<List<Object>>結構的數據
     * <p>3. 寫數據
     */
    @Test
    public void listWrite() {
        List<List<String>> head = new ArrayList<>();
        head.add(Arrays.asList("head1"));
        head.add(Arrays.asList("head2"));
        head.add(Arrays.asList("head3"));
        head.add(Arrays.asList("head4"));

        List<List<Object>> data = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            data.add(Arrays.asList("字符串" + i, new Date(), 0.56, (i % 2 == 0)));
        }

        EasyExcel.write("./test.xlsx")
                .excelType(ExcelTypeEnum.XLSX)
                .head(head)
                .sheet("demoData")
                .doWrite(data);
    }

Reader

讀一個sheet

   /**
     * 異步讀
     * <p>
     * 1. 由於默認異步讀取excel,所以需要創建excel一行一行的回調監聽器,參照{@link ObjectListener}
     * <p>
     * 2. 直接讀即可
     */
    @Test
    public void read() {
        ObjectListener listener = new ObjectListener();
        // 這裏 需要指定監聽器用於存儲數據,然後讀取第一個sheet 文件流會自動關閉
        EasyExcel.read("./test.xlsx", listener)
                // 0 是第一個sheet的index
				.sheet(0)
                // 同步讀 doReadSync()
                // 異步讀 doRead()
                .doRead();
        System.out.println(listener.getList().stream().count());
        listener.getList().stream().forEach(System.out::println);
		// 2
		// {0=自定義:字符串0, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=可用}
		// {0=自定義:字符串1, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=禁用}
   }

輸出

 2
 {0=自定義:字符串0, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=可用}
 {0=自定義:字符串1, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=禁用}

讀全部sheet

   /**
     * @description: 讀取所有的sheet數據
     * @author: tanpeng
     * @date : 2020-02-13 10:50 
     * @version: v1.0.0
     */
    @Test
    public void readAll() {
        ObjectListener listener = new ObjectListener();
        // 這裏 需要指定監聽器用於存儲數據,然後讀取第一個sheet 文件流會自動關閉
        EasyExcel.read("./test.xlsx", listener)
                .doReadAll();
        System.out.println(listener.getList().stream().count());
        listener.getList().stream().forEach(System.out::println);
    }

輸出

 4
 {0=自定義:字符串0, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=可用}
 {0=自定義:字符串1, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=禁用}
 {0=自定義:字符串0, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=可用}
 {0=自定義:字符串1, 1=2020年02月13日 09時32分29秒, 2=0.56, 3=禁用}

Listener

正確使用 Listener

  • invoke 數據校驗、數據轉換等工作
  • doAfterAllAnalysed 整個excel解析結束時執行
/**
 * 解析監聽器,
 * 每解析一行會回調invoke()方法。
 * 整個excel解析結束會執行doAfterAllAnalysed()方法
 * @description:
 * @author: tanpeng
 * @date: 2020-02-07 14:55
 * @version: v1.0.0
 */
@Data
public class ConverterDataListener extends AnalysisEventListener<ConverterData> {

    @Override
    public void invoke(ConverterData data, AnalysisContext context) {
        // 數據校驗、數據轉換等工作
        if (data != null) {
            if (data.getString().contains("5")) {
                // 不處理本條數據
                // return;
                // 直接結束數據處理,不再處理後面的數據 throw new RuntimeException()
                throw new RuntimeException("包含數字5");
            } else {
                // 處理數據
                System.out.println(data.toString());
            }
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 處理完畢
        System.out.println("done");
    }
}

錯誤使用 Listener

新同學很容易,會像下面的代碼一樣使用ObjectListener,聲明一個空的list容器用來保存invoke中返回的每條數據,這樣其實又是將數據放在了內存中進行計算,當解析大文件時,同樣會OOM

實際上easyexcel使用 SAX 模式解析,每次只返回一條數據。
返回的這一條數據就是invoke方法的第一個參數,invoke方法中應該做數據校驗、數據轉換等工作,而不是將數據又逐條往內存裏放,放到最後說不定又到了OOM的局面

/**
 * 解析監聽器,
 * 每解析一行會回調invoke()方法。
 * 整個excel解析結束會執行doAfterAllAnalysed()方法
 * @description:
 * @author: tanpeng
 * @date: 2020-02-07 13:00
 * @version: v1.0.0
 */
@Data
public class ObjectListener extends AnalysisEventListener<Object> {

    /**
     * @description: 默認範型爲 LinkedHashMap<Integer, String> {@link MapCache}
     * @author: tanpeng
     * @date : 2020-02-07 14:33
     * @version: v1.0.0
     */
    private List<Object> list = new ArrayList<>();

    @Override
    public void invoke(Object data, AnalysisContext context) {
        if (data != null) {
            list.add(data);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }
}

工作原理

easyexcel 的工作原理圖,它清楚的告訴了你爲什麼使用easyexcel可以避免OOM

  • 1、文件解壓文件讀取通過文件形式
    在這裏插入圖片描述
  • 2、避免將全部全部數據一次加載到內存
    在這裏插入圖片描述
  • 3、拋棄不重要的數據
    Excel解析時候會包含樣式,字體,寬度等數據,但這些數據是我們不關心的,如果將這部分數據拋棄可以大大降低內存使用。Excel中數據如下Style佔了相當大的空間。

工程結構

在這裏插入圖片描述

其他

實體類可使用的註解

在這裏插入圖片描述

Excel 限制

版本 最大行 最大列
Excel 2003 .xls 65536 256
Excel 2007 .xlsx 1048576 16384
不支持csv的讀寫 - -

CellDataTypeEnum枚舉類 學習

枚舉類 可以看作是關係型數據庫中的一張表

package com.alibaba.excel.enums;

import java.util.HashMap;
import java.util.Map;

import com.alibaba.excel.util.StringUtils;

/**
 * excel internal data type
 *
 * @author Jiaju Zhuang
 */
public enum CellDataTypeEnum {
    /**
     * string
     */
    STRING,
    /**
     * This type of data does not need to be read in the 'sharedStrings.xml', it is only used for overuse, and the data
     * will be stored as a {@link #STRING}
     */
    DIRECT_STRING,
    /**
     * number
     */
    NUMBER,
    /**
     * boolean
     */
    BOOLEAN,
    /**
     * empty
     */
    EMPTY,
    /**
     * error
     */
    ERROR,
    /**
     * Images are currently supported only when writing
     */
    IMAGE;

    private static final Map<String, CellDataTypeEnum> TYPE_ROUTING_MAP = new HashMap<String, CellDataTypeEnum>(16);
    static {
        TYPE_ROUTING_MAP.put("s", STRING);
        TYPE_ROUTING_MAP.put("str", DIRECT_STRING);
        TYPE_ROUTING_MAP.put("inlineStr", STRING);
        TYPE_ROUTING_MAP.put("e", ERROR);
        TYPE_ROUTING_MAP.put("b", BOOLEAN);
        TYPE_ROUTING_MAP.put("n", NUMBER);
    }

    /**
     * Build data types
     *
     * @param cellType
     * @return
     */
    public static CellDataTypeEnum buildFromCellType(String cellType) {
        if (StringUtils.isEmpty(cellType)) {
            return EMPTY;
        }
        return TYPE_ROUTING_MAP.get(cellType);
    }
}

完整代碼

完整代碼 請移步gitee

發佈了97 篇原創文章 · 獲贊 44 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章