EasyExcel——简单读取Excel
温馨提示:
本文最后更新于 2024年01月09日,已超过 348 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我。
目录
Easy Excel 读取
1、读取单个sheet
1.1、方式一
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
/** * 写法1:JDK8+ ,不用额外写一个ReadListener,使用自带的PageReadListener */
@Test
public void simpleRead1() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行
// 具体需要返回多少行可以在`PageReadListener`的构造函数设置
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(new Consumer<List<DemoData>>() {
@Override
public void accept(List<DemoData> list) {
for (DemoData demoData : list) {
System.out.println(demoData);
}
}
}, 100)).sheet().doRead();
}
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
1.2、方式二
/** * 写法2:匿名内部类 不用额外写一个ReadListener */
@Test
public void simpleRead2() {
String fileName = "./src/main/resources/demo.xlsx";
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
//单次缓存的数据量
public static final int BATCH_COUNT = 3;
//缓存
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(DemoData data, AnalysisContext analysisContext) {
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
//处理业务
for (DemoData d : cachedDataList) {
System.out.println(d);
}
System.out.println("*********");
// 存储完成清理 list
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//处理业务
for (DemoData d : cachedDataList) {
System.out.println(d);
}
System.out.println("*********");
}
}).sheet().doRead();
}
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
*********
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
*********
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
*********
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
*********
1.3、方式三
//DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener implements ReadListener<DemoData> {
//每次读取5条数据,然后清理缓存List,防止OOM
private static final int BATCH_COUNT = 5;
//缓存的数据
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//Dao层,如果有业务逻辑,也可以是一个Service
private DemoDao demoDao;
//无参构造
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDao = new DemoDao();
}
//如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
public DemoDataListener(DemoDao demoDao) {
this.demoDao = demoDao;
}
/** * 每一条数据解析都会调用该方法 * @param demoData * @param analysisContext */
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析到一条数据。。。");
cachedDataList.add(demoData);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
//保存数据
saveData();
//清理缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/** * 所有数据全部解析完毕后,会调用该方法 * @param analysisContext */
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
System.out.println("所有数据解析完毕");
}
private void saveData() {
this.demoDao.batchInsert(cachedDataList);
}
}
public class DemoDao {
//模拟Dao层批量保存方法
public void batchInsert(List<DemoData> list) {
System.out.println("本批次数据数量:" + list.size());
list.forEach(System.out::println);
}
}
/** * 写法3:DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 */
@Test
public void simpleRead3() {
InputStream is = this.getClass().getClassLoader().getResourceAsStream("demo.xlsx");
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(is, DemoData.class, new DemoDataListener()).sheet().doRead();
}
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
本批次数据数量:0
所有数据解析完毕
1.4、方式四
/** * 写法4: */
@Test
public void simpleRead4() {
String fileName = "./src/main/resources/demo.xlsx";
//一个文件一个reader
try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
}
}
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
本批次数据数量:0
所有数据解析完毕
2、读多个sheet
2.1、方式一
/** * 方式一:使用同一个ReadListener读取 */
@Test
public void readAllTest1() {
String fileName = "./src/main/resources/demo.xlsx";
// 读取全部sheet
// 这里需要注意 ReadListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个ReadListener里面写
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(list -> {
for (DemoData data :list) {
System.out.println(data);
}
System.out.println("********");
}, 4)).doReadAll();
}
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
********
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
********
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
********
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
DemoData(string=字符串00, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串11, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
********
DemoData(string=字符串22, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串33, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串44, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串55, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
********
DemoData(string=字符串66, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串77, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串88, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串99, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
********
注意:此种情况下,每个Sheet读取完毕后,都会调用一次doAfterAllAnalysed方法,如果excel的行数不刚好是缓存大小的整数倍,每个sheet最后一次读取的数据就会有残留,导致下一个sheet读取后,会多处理一遍残留的数据。比如示例中:每个Sheet10行数据,缓存大小为4,就会导致sheet0的最后两条数据8、9,在读取sheet1的时候又被处理一次,存在一定问题。推荐使用下面的方式二。
2.2、方式二
/** * 方式二:每个Sheet一个ReadListener */
@Test
public void readAllTest2() {
String fileName = "./src/main/resources/demo.xlsx";
try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
ReadSheet readSheet1 = EasyExcel.readSheet(0)
.head(DemoData.class)
.registerReadListener(new PageReadListener<DemoData>(list -> {
for (DemoData data :list) {
System.out.println(data);
}
System.out.println("********");
}, 4))
.build();
ReadSheet readSheet2 = EasyExcel.readSheet(1)
.head(DemoData.class)
.registerReadListener(new PageReadListener<DemoData>(list -> {
for (DemoData data :list) {
System.out.println(data);
}
System.out.println("********");
}, 4))
.build();
// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
excelReader.read(readSheet1, readSheet2);
}
}
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
********
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
********
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
********
DemoData(string=字符串00, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串11, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串22, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串33, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
********
DemoData(string=字符串44, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串55, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串66, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串77, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
********
DemoData(string=字符串88, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串99, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
********
3、指定列的下标或列名
@Data
public class IndexOrNameData {
/** * 强制读取第三个 这里不建议 index 和 name 同时用, * 要么一个对象只用index,要么一个对象只用name去匹配 */
@ExcelProperty(index = 2)
private Double doubleData;
/** * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 */
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
@Test
public void indexOrNameTest() {
String fileName = "./src/main/resources/demo.xlsx";
EasyExcel.read(fileName, IndexOrNameData.class, new PageReadListener<IndexOrNameData>(list -> {
for (IndexOrNameData data :list) {
System.out.println(data);
}
}, 100)).sheet().doRead();
}
IndexOrNameData(doubleData=1.0, string=字符串0, date=Wed Jan 01 01:01:00 CST 2020)
IndexOrNameData(doubleData=2.0, string=字符串1, date=Thu Jan 02 01:01:00 CST 2020)
IndexOrNameData(doubleData=3.0, string=字符串2, date=Fri Jan 03 01:01:00 CST 2020)
IndexOrNameData(doubleData=4.0, string=字符串3, date=Sat Jan 04 01:01:00 CST 2020)
IndexOrNameData(doubleData=5.0, string=字符串4, date=Sun Jan 05 01:01:00 CST 2020)
IndexOrNameData(doubleData=6.0, string=字符串5, date=Mon Jan 06 01:01:00 CST 2020)
IndexOrNameData(doubleData=7.0, string=字符串6, date=Tue Jan 07 01:01:00 CST 2020)
IndexOrNameData(doubleData=8.0, string=字符串7, date=Wed Jan 08 01:01:00 CST 2020)
IndexOrNameData(doubleData=9.0, string=字符串8, date=Thu Jan 09 01:01:00 CST 2020)
IndexOrNameData(doubleData=10.0, string=字符串9, date=Fri Jan 10 01:01:00 CST 2020)
4、日期、数字或者自定义格式转换
@Data
public class ConverterData {
/** * 自定义转换器,不管数据库传过来什么。给他加上“自定义:” */
@ExcelProperty(converter = CustomStringStringConverter.class)
private String string;
/** * 这里用string 去接日期才能格式化。 */
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;
/** * 我想接收百分比的数字 */
@NumberFormat("#.##%")
private String doubleData;
}
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class<?> supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/** * 这里读的时候会调用 * * @param context * @return */
@Override
public String convertToJavaData(ReadConverterContext<?> context) {
return "自定义:" + context.getReadCellData().getStringValue();
}
/** * 这里是写的时候会调用 不用管 * * @return */
@Override
public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
return new WriteCellData<>(context.getValue());
}
}
/** * 日期、数字或者自定义格式转换 * <p> * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} */
@Test
public void converterRead() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
// 如果就想单个字段使用请使用@ExcelProperty 指定converter
// .registerConverter(new CustomStringStringConverter())
EasyExcel.read(fileName, ConverterData.class, new PageReadListener<ConverterData>(list -> {
for (ConverterData data : list) {
System.out.println(data);
}
})).sheet().doRead();
}
ConverterData(string=自定义:字符串0, date=2020年01月01日01时01分00秒, doubleData=100%)
ConverterData(string=自定义:字符串1, date=2020年01月02日01时01分00秒, doubleData=200%)
ConverterData(string=自定义:字符串2, date=2020年01月03日01时01分00秒, doubleData=300%)
ConverterData(string=自定义:字符串3, date=2020年01月04日01时01分00秒, doubleData=400%)
ConverterData(string=自定义:字符串4, date=2020年01月05日01时01分00秒, doubleData=500%)
ConverterData(string=自定义:字符串5, date=2020年01月06日01时01分00秒, doubleData=600%)
ConverterData(string=自定义:字符串6, date=2020年01月07日01时01分00秒, doubleData=700%)
ConverterData(string=自定义:字符串7, date=2020年01月08日01时01分00秒, doubleData=800%)
ConverterData(string=自定义:字符串8, date=2020年01月09日01时01分00秒, doubleData=900%)
ConverterData(string=自定义:字符串9, date=2020年01月10日01时01分00秒, doubleData=1000%)
5、多行头
@Test
public void complexHeaderRead() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(list -> {
for (DemoData data : list) {
System.out.println(data);
}
})).sheet().headRowNumber(3).doRead();
// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。
// 里要注意headRowNumber如果不指定, 会根据你传入的class的ExcelProperty#value()里面的表头的数量来决定行数,
// 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
}
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
6、同步的返回
6.1、方式一
/** * 方式一:同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 */
@Test
public void synchronousRead1() {
String fileName = "./src/main/resources/demo.xlsx";
List<DemoData> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
for (DemoData data : list) {
System.out.println(data);
}
}
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
6.2、方式二
/** * 方式二:同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 */
@Test
public void synchronousRead2() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();
for (Map<Integer, String> data : listMap) {
// 返回每条数据的键值对 表示所在的列 和所在列的值
System.out.println(data);
}
}
{
0=字符串0, 1=2020-1-1 1:01, 2=1}
{
0=字符串1, 1=2020-1-2 1:01, 2=2}
{
0=字符串2, 1=2020-1-3 1:01, 2=3}
{
0=字符串3, 1=2020-1-4 1:01, 2=4}
{
0=字符串4, 1=2020-1-5 1:01, 2=5}
{
0=字符串5, 1=2020-1-6 1:01, 2=6}
{
0=字符串6, 1=2020-1-7 1:01, 2=7}
{
0=字符串7, 1=2020-1-8 1:01, 2=8}
{
0=字符串8, 1=2020-1-9 1:01, 2=9}
{
0=字符串9, 1=2020-1-10 1:01, 2=10}
7、读表头数据
public class DemoHeadDataListener implements ReadListener<DemoData> {
//每次读取5条数据,然后清理缓存List,防止OOM
private static final int BATCH_COUNT = 5;
//缓存的数据
private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
//Dao层,如果有业务逻辑,也可以是一个Service
private DemoDao demoDao;
//无参构造
public DemoHeadDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDao = new DemoDao();
}
//如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
public DemoHeadDataListener(DemoDao demoDao) {
this.demoDao = demoDao;
}
/** * 每一条数据解析都会调用该方法 * @param demoData * @param analysisContext */
@Override
public void invoke(DemoData demoData, AnalysisContext analysisContext) {
System.out.println("解析到一条数据。。。");
cachedDataList.add(demoData);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
//保存数据
saveData();
//清理缓存
cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/** * 所有数据全部解析完毕后,会调用该方法 * @param analysisContext */
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
System.out.println("所有数据解析完毕");
}
private void saveData() {
this.demoDao.batchInsert(cachedDataList);
}
/** * 这里在读到请求头的时候,会一行行返回 * @param headMap * @param context */
@Override
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
// 如果想转成成 Map<Integer,String>
// 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener
// 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换
Map<Integer, String> map = ConverterUtils.convertToStringMap(headMap, context);
System.out.println("表头:" + map);
}
}
/** * 读取表头 */
@Test
public void headerRead() {
String fileName = "./src/main/resources/demo.xlsx";
EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener())
.sheet().doRead();
}
表头:{
0=字符串标题, 1=日期标题, 2=数字标题}
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串0, date=Wed Jan 01 01:01:00 CST 2020, doubleData=1.0)
DemoData(string=字符串1, date=Thu Jan 02 01:01:00 CST 2020, doubleData=2.0)
DemoData(string=字符串2, date=Fri Jan 03 01:01:00 CST 2020, doubleData=3.0)
DemoData(string=字符串3, date=Sat Jan 04 01:01:00 CST 2020, doubleData=4.0)
DemoData(string=字符串4, date=Sun Jan 05 01:01:00 CST 2020, doubleData=5.0)
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
解析到一条数据。。。
本批次数据数量:5
DemoData(string=字符串5, date=Mon Jan 06 01:01:00 CST 2020, doubleData=6.0)
DemoData(string=字符串6, date=Tue Jan 07 01:01:00 CST 2020, doubleData=7.0)
DemoData(string=字符串7, date=Wed Jan 08 01:01:00 CST 2020, doubleData=8.0)
DemoData(string=字符串8, date=Thu Jan 09 01:01:00 CST 2020, doubleData=9.0)
DemoData(string=字符串9, date=Fri Jan 10 01:01:00 CST 2020, doubleData=10.0)
本批次数据数量:0
所有数据解析完毕
8、额外信息(批注、超链接、合并单元格信息读取
@Data
public class DemoExtraData {
private String row1;
private String row2;
}
public class DemoExtraListener implements ReadListener<DemoExtraData> {
@Override
public void invoke(DemoExtraData demoExtraData, AnalysisContext analysisContext) {
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
System.out.println("读取到了一条额外信息:" );
System.out.println("text:" + extra.getText());
System.out.println("type:" + extra.getType());
System.out.println("rowIndex:" + extra.getRowIndex());
System.out.println("columnIndex:" + extra.getColumnIndex());
System.out.println("firstRow-lastRow:" + extra.getFirstRowIndex() + "-" + extra.getLastRowIndex());
System.out.println("firstColumn-lastColumn" + extra.getFirstColumnIndex() + "-" + extra.getLastColumnIndex());
System.out.println("*********************************");
}
}
/** * 额外信息(批注、超链接、合并单元格信息读取) * 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息, * 所以只能最后通知哪些单元格有哪些额外信息 */
@Test
public void extraRead() {
String fileName = "./src/main/resources/demo.xlsx";
EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener())
// 需要读取批注 默认不读取
.extraRead(CellExtraTypeEnum.COMMENT)
// 需要读取超链接 默认不读取
.extraRead(CellExtraTypeEnum.HYPERLINK)
// 需要读取合并单元格信息 默认不读取
.extraRead(CellExtraTypeEnum.MERGE).sheet(2).doRead();
}
读取到了一条额外信息:
text:null
type:MERGE
rowIndex:5
columnIndex:0
firstRow-lastRow:5-6
firstColumn-lastColumn0-1
*********************************
读取到了一条额外信息:
text:http://www.baidu.com/
type:HYPERLINK
rowIndex:1
columnIndex:0
firstRow-lastRow:1-1
firstColumn-lastColumn0-0
*********************************
读取到了一条额外信息:
text:http:www.baidu.comA
type:HYPERLINK
rowIndex:2
columnIndex:0
firstRow-lastRow:2-2
firstColumn-lastColumn0-0
*********************************
读取到了一条额外信息:
text:http://www.baidu.comc/
type:HYPERLINK
rowIndex:3
columnIndex:0
firstRow-lastRow:3-3
firstColumn-lastColumn0-0
*********************************
读取到了一条额外信息:
text:http://www.baidu.comb/
type:HYPERLINK
rowIndex:2
columnIndex:1
firstRow-lastRow:2-2
firstColumn-lastColumn1-1
*********************************
读取到了一条额外信息:
text:http://www.baidu.comd/
type:HYPERLINK
rowIndex:3
columnIndex:1
firstRow-lastRow:3-3
firstColumn-lastColumn1-1
*********************************
读取到了一条额外信息:
text:批注
type:COMMENT
rowIndex:4
columnIndex:0
firstRow-lastRow:4-4
firstColumn-lastColumn0-0
*********************************
9、读取公式和单元格类型
@Data
public class CellDataReadDemoData {
private CellData<String> string;
// 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number
private CellData<Date> date;
private CellData<Double> doubleData;
// 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复
private CellData<String> formulaValue;
}
@Test
public void cellDataRead() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet
EasyExcel.read(fileName, CellDataReadDemoData.class, new PageReadListener<CellDataReadDemoData>(list -> {
for (CellDataReadDemoData data : list) {
System.out.println(data.getString().getType() + "=" + data.getString().getStringValue());
System.out.println(data.getDate().getType() + "=" + data.getDate().getData());
System.out.println(data.getDoubleData().getType() + "=" + data.getDoubleData().getNumberValue());
System.out.println(data.getFormulaValue().getType() + "=" + data.getFormulaValue().getFormulaData().getFormulaValue());
System.out.println("***************");
}
})).sheet().doRead();
}
STRING=字符串0
NUMBER=Wed Jan 01 01:01:00 CST 2020
NUMBER=1
STRING=_xlfn.CONCAT(A2,C2)
***************
STRING=字符串1
NUMBER=Thu Jan 02 01:01:00 CST 2020
NUMBER=2
STRING=_xlfn.CONCAT(A3,C3)
***************
STRING=字符串2
NUMBER=Fri Jan 03 01:01:00 CST 2020
NUMBER=3
STRING=
***************
STRING=字符串3
NUMBER=Sat Jan 04 01:01:00 CST 2020
NUMBER=4
STRING=
***************
STRING=字符串4
NUMBER=Sun Jan 05 01:01:00 CST 2020
NUMBER=5
STRING=
***************
STRING=字符串5
NUMBER=Mon Jan 06 01:01:00 CST 2020
NUMBER=6
STRING=
***************
STRING=字符串6
NUMBER=Tue Jan 07 01:01:00 CST 2020
NUMBER=7
STRING=
***************
STRING=字符串7
NUMBER=Wed Jan 08 01:01:00 CST 2020
NUMBER=8
STRING=
***************
STRING=字符串8
NUMBER=Thu Jan 09 01:01:00 CST 2020
NUMBER=9
STRING=
***************
STRING=字符串9
NUMBER=Fri Jan 10 01:01:00 CST 2020
NUMBER=10
STRING=
***************
10、异常处理
@Getter
@Setter
@EqualsAndHashCode
public class ExceptionDemoData {
/** * 用日期去接字符串 肯定报错 */
private Date date;
}
重写onException方法即可
/** * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 * * @param exception * @param context * @throws Exception */
@Override
public void onException(Exception exception, AnalysisContext context) {
log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
}
}
11、不创建对象的读
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
private static final int BATCH_COUNT = 5;
private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(Map<Integer, String> data, AnalysisContext context) {
System.out.println("解析到一条数据...");
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
cachedDataList =ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
System.out.println("所有数据解析完成");
}
private void saveData() {
for (Map<Integer, String> data : cachedDataList) {
System.out.println(data);
}
}
}
/** * 不创建对象的读 */
@Test
public void noModelRead() {
String fileName = "./src/main/resources/demo.xlsx";
// 这里 只要,然后读取第一个sheet 同步读取会自动finish
EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead();
}
```
```java
解析到一条数据...
解析到一条数据...
解析到一条数据...
解析到一条数据...
解析到一条数据...
{
0=字符串0, 1=2020-1-1 1:01, 2=1, 3=字符串01}
{
0=字符串1, 1=2020-1-2 1:01, 2=2, 3=字符串12}
{
0=字符串2, 1=2020-1-3 1:01, 2=3, 3=字符串23}
{
0=字符串3, 1=2020-1-4 1:01, 2=4, 3=字符串34}
{
0=字符串4, 1=2020-1-5 1:01, 2=5, 3=字符串45}
解析到一条数据...
解析到一条数据...
解析到一条数据...
解析到一条数据...
解析到一条数据...
{
0=字符串5, 1=2020-1-6 1:01, 2=6, 3=字符串56}
{
0=字符串6, 1=2020-1-7 1:01, 2=7, 3=字符串67}
{
0=字符串7, 1=2020-1-8 1:01, 2=8, 3=字符串78}
{
0=字符串8, 1=2020-1-9 1:01, 2=9, 3=字符串89}
{
0=字符串9, 1=2020-1-10 1:01, 2=10, 3=字符串910}
所有数据解析完成
```
## 12、web中的读
```java
/** * 文件上传 * <p> * 1. 创建excel对应的实体对象 参照{@link UploadData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} * <p> * 3. 直接读即可 */
@PostMapping("upload")
@ResponseBody
public String upload(MultipartFile file) throws IOException {
EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();
return "success";
}
```
正文到此结束