【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

发布了105 篇原创文章 · 获赞 46 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/AV_woaijava/article/details/104293882