EasyExcel:快速读写Excel的工具类

EasyExcel:快速读写Excel的工具类

项目介绍

EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel 处理工具。

他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。

pom地址


xml 复制代码
<!--exel-->
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel</artifactId>
	<version>4.0.3</version>
</dependency>

快速入门


简单读

读取excel的操作,主要如下:

  1. 创建对应数据对象(映射表格中的列)
  2. 创建一个xxxListener类(可以使用匿名内部类替代)
  3. 创建输入流(或者其他io方式)
  4. 调用EasyExcel方法进行读取

test.xlsx,使用此文件进行读取,放入resources下使用

步骤一:创建数据对象

举例:DemoData

java 复制代码
@Data
public class DemoData {
    private Long id;
    private String nickName;
    private Double score;
}
  • @Data是Lombok[^1]中的方法,可以快速生成setter和getter以及toString等

步骤二:创建Listener

举例:DemoDataListener

java 复制代码
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    /**
     * 缓存的数据
     */
    private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 每读一条数据解析就会调用此方法
     * @param demoData
     * @param analysisContext
     */
    @Override
    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
        Gson gson = new Gson();
        log.info("解析到一条数据:{}", gson.toJson(demoData));
        cachedDataList.add(demoData);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 保存数据到数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        log.info("存储数据库成功!");
    }

}
  • Listener可以对解析的数据进行更高自由度的操作:如 写入数据到数据库

步骤三:创建输入流

++接下来的代码和步骤四都是一个代码中的++,此阶段为调用。

创建输入流代码块:

java 复制代码
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test.xlsx")) {
            ...
} 
catch (Exception e) {
	e.printStackTrace();
}

步骤四:调用方法

tips

这里举例的是其中一种调用方法,详情请参考:完整读取代码案例

java 复制代码
EasyExcel.read(inputStream, DemoData.class, new PageReadListener<DemoData>(dataList -> {
	dataList.forEach(data -> {
		log.info("读取到数据:{}", data);
	});
})).sheet().doRead();

完整读取代码案例

ReadTest.java

java 复制代码
@Slf4j
public class ReadTest {

    /**
     * 方法1:简单读
     * 1. 创建excel对应的对象 参照
     * 2. 由于默认一行行读取excel,所以需要创建excel一行一行的回调监听器
     * 3. 直接读即可
     */
    @Test
    public void simpleRead() {
        try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test.xlsx")) {
            EasyExcel.read(inputStream, DemoData.class, new PageReadListener<DemoData>(dataList -> {
                dataList.forEach(data -> {
                    log.info("读取到数据:{}", data);
                });
            })).sheet().doRead();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 方法2:简单读(匿名内部类)
     * 优化点:更多自定义空间,如:读取过程中添加存储到数据库
     * 1. 创建excel对应的对象 参照
     * 2. 由于默认一行行读取excel,所以需要创建excel一行一行的回调监听器
     * 3. 直接读即可
     */
    @Test
    public void simpleRead2() {
        try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test.xlsx")) {
            EasyExcel.read(inputStream, DemoData.class, new ReadListener<DemoData>() {
                /**
                 * 单次缓存数据量
                 */
                private static final int BATCH_COUNT = 100;
                /**
                 * 临时存储
                 */
                private List<DemoData> cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

                @Override
                public void invoke(DemoData demoData, AnalysisContext analysisContext) {
                    cacheDataList.add(demoData);
                    log.info("读取到数据:{}", demoData);
                    if (cacheDataList.size() >= BATCH_COUNT) {
                        saveData();
                        // 存储完成清理 list
                        cacheDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
                    }
                }

                @Override
                public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                    saveData();
                }

                /**
                 * 模拟存储数据库
                 */
                private void saveData() {
                    log.info("{}条数据, 开始存储数据库!", cacheDataList.size());
                    log.info("数据库存储成功!");
                }
            }).sheet().doRead();
        } catch (IOException e) {
            log.error("IOException: {}", e.getMessage());
        }
    }

    /**
     * 其余两个最简单的写法
     */
    @Test
    public void simpleRead3() {
        try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test.xlsx")) {
            // 写法1
            // 由于流使用后会自动关闭,所以写法1和2要分开运行
//            EasyExcel.read(inputStream, DemoData.class, new DemoDataListener()).sheet().doRead();

            // 写法2
            try (ExcelReader excelReader = EasyExcel.read(inputStream, DemoData.class, new DemoDataListener()).build()) {
                // 构建一个sheet 可以指定明知或者sheetNo
                ReadSheet sheet = EasyExcel.readSheet(0).build();
                // 读取
                excelReader.read(sheet);
            }
        } catch (IOException e) {
            log.error("IOException: {}", e.getMessage());
        }
    }

}

指定索引或列名读取

和简单读差不多,主要修改在 Data 上,需要对映射的属性使用 @ExcelProperty 进行配置。

这里使用表格:test1.xlsx

步骤一:创建数据对象

IndexOrNameData

java 复制代码
@Data
public class IndexOrNameData {
    /**
     * 强制读取第三个
     * 一般不建议 index 和 name 同时用
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会只读取第一个
     *
     * @ExcelProperty("字符串标题")
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;

}

步骤二:创建Listener

IndexOrNameDataListener

java 复制代码
@Slf4j
public class IndexOrNameDataListener extends AnalysisEventListener<IndexOrNameData> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    Gson gson = new Gson();
    private List<IndexOrNameData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    @Override
    public void invoke(IndexOrNameData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", gson.toJson(data));
        cachedDataList.add(data);
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        log.info("存储数据库成功!");
    }
}

步骤三:创建输入流以及调用

java 复制代码
/**
* 指定列的下标或列名
 * 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解
 * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器
 * 3. 直接读即可
 */
@Test
public void indexOrNameRead() {
    try (InputStream in = getClass().getClassLoader().getResourceAsStream("test1.xlsx")) {
        EasyExcel.read(in, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
    } catch (IOException e) {
        log.error("IOException: {}", e.getMessage());
    }
}
复制代码
相关推荐
小蒜学长29 分钟前
springboot基于BS的小区家政服务预约平台(代码+数据库+LW)
java·数据库·spring boot·后端
zhangfeng11332 小时前
生物信息 R语言和 cytoscape 相互沟通的组件RCy3,构建cytoscape网络表 节点类型表 链接边的表,并推送到cytoscape
数据库·r语言·生物信息
小森( ﹡ˆoˆ﹡ )2 小时前
GPT_Data_Processing_Tutorial
数据库·gpt·mysql
krielwus3 小时前
Oracle Linux 7.8 静默安装 Oracle 11g R2 单机 ASM 详细教程
数据库·oracle
翔云1234564 小时前
向量数据库的几个核心概念
数据库
sniper_fandc4 小时前
关于Mybatis-Plus的insertOrUpdate()方法使用时的问题与解决—数值精度转化问题
java·前端·数据库·mybatisplus·主键id
lang201509284 小时前
MySQL在线DDL:零停机改表实战指南
数据库·mysql
程序新视界4 小时前
MySQL的联合索引以及其最左前缀原则
数据库·mysql
奥尔特星云大使4 小时前
mysql 全备+binlog恢复数据
数据库·mysql·adb·数据恢复·全量备份·binlog日志·二进制日志
the beard4 小时前
Redis Zset的底层秘密:跳表(Skip List)的精妙设计
数据库·redis·list