EasyExcel:快速读写Excel的工具类
项目介绍
EasyExcel是一个基于Java的、快速、简洁、解决大文件内存溢出的Excel处理工具。
他能让你在不用考虑性能、内存的等因素的情况下,快速完成Excel的读、写等功能。
pom地址
<!--exel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>4.0.3</version>
</dependency>
快速入门
简单读
读取excel的操作,主要如下:
test.xlsx,使用此文件进行读取,放入resources
下使用
步骤一:创建数据对象
举例:DemoData
@Data
public class DemoData {
private Long id;
private String nickName;
private Double score;
}
-
@Data
是Lombok[^1]中的方法,可以快速生成setter和getter以及toString等
步骤二:创建Listener
举例:DemoDataListener
@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可以对解析的数据进行更高自由度的操作:如
写入数据到数据库
步骤三:创建输入流
接下来的代码和步骤四都是一个代码中的,此阶段为调用。
创建输入流代码块:
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("test.xlsx")) {
...
}
catch (Exception e) {
e.printStackTrace();
}
步骤四:调用方法
tips
:这里举例的是其中一种调用方法,详情请参考:完整读取代码案例
EasyExcel.read(inputStream, DemoData.class, new PageReadListener<DemoData>(dataList -> {
dataList.forEach(data -> {
log.info("读取到数据:{}", data);
});
})).sheet().doRead();
完整读取代码案例
ReadTest.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
:
@Data
public class IndexOrNameData {
/**
* 强制读取第三个
* 一般不建议 index 和 name 同时用
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会只读取第一个
*
* @ExcelProperty("字符串标题")
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
步骤二:创建Listener
IndexOrNameDataListener
:
@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("存储数据库成功!");
}
}
步骤三:创建输入流以及调用
/**
* 指定列的下标或列名
* 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());
}
}