【EasyExcel】十分钟入门 EasyExcel

EasyExcel快速入门
本文介绍如何使用alibaba/easyexcel库高效处理Excel文件,避免内存溢出问题,包括使用对象和列表写入数据,以及异步读取和解析Excel文件的方法。

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 .xls65536256
Excel 2007 .xlsx104857616384
不支持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

EasyExcel 是一个基于阿里巴巴的开源项目 easyexcel 开发的 Excel 工具类库,可以方便快捷地读取、写入 Excel 文件。它支持大批量数据的导入导出,可以读取 Excel 中的复杂表格,并且能够将 Java 对象映射到 Excel 单元格中。 在 EasyExcel 中,图片的导入和导出可以通过一个 POJO 类来实现。下面是一个示例: ```java public class ImageData { //图片文件名 private String fileName; //图片字节数组 private byte[] imageBytes; public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public byte[] getImageBytes() { return imageBytes; } public void setImageBytes(byte[] imageBytes) { this.imageBytes = imageBytes; } } ``` 这个 POJO 类包含了图片文件名和图片字节数组两个属性,分别用于表示图片的文件名和内容。在导入和导出 Excel 文件时,我们可以使用 EasyExcel 提供的注解来指定这个 POJO 类中的属性和 Excel 表格中的列之间的映射关系。 下面是一个示例,演示了如何将 POJO 类中的图片数据导出到 Excel 文件中: ```java public static void writeExcel() throws IOException { //定义 Excel 文件输出流 OutputStream outputStream = new FileOutputStream("test.xlsx"); //创建 EasyExcel 写入器 ExcelWriter excelWriter = EasyExcel.write(outputStream, ImageData.class).build(); //创建一个 Sheet WriteSheet writeSheet = EasyExcel.writerSheet("Sheet1").build(); //创建一个图片 BufferedImage image = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setColor(Color.WHITE); g.fillRect(0, 0, 100, 100); g.setColor(Color.BLACK); g.drawString("Hello, EasyExcel", 10, 50); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ImageIO.write(image, "png", outputStream); byte[] bytes = outputStream.toByteArray(); //创建一个 ImageData 对象 ImageData imageData = new ImageData(); imageData.setFileName("test.png"); imageData.setImageBytes(bytes); //将 ImageData 对象写入到 Excel 文件中 excelWriter.write(Arrays.asList(imageData), writeSheet); //关闭 Excel 写入器 excelWriter.finish(); } ``` 在这个示例中,我们首先创建了一个 BufferedImage 对象,然后使用 Graphics2D 绘制了一个字符串,并将其转换为了字节数组。接着,我们创建了一个 ImageData 对象,并将图片文件名和字节数组分别设置到了对象的属性中。最后,我们将 ImageData 对象写入到了 Excel 文件中。 导入 Excel 文件中包含图片数据的过程与导出类似,只需要使用 EasyExcel 提供的读取器和监听器,读取 Excel 文件中的数据,并将读取到的数据转换为 POJO 对象即可。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值