转载

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=20200101010100, doubleData=100%)
ConverterData(string=自定义:字符串1, date=20200102010100, doubleData=200%)
ConverterData(string=自定义:字符串2, date=20200103010100, doubleData=300%)
ConverterData(string=自定义:字符串3, date=20200104010100, doubleData=400%)
ConverterData(string=自定义:字符串4, date=20200105010100, doubleData=500%)
ConverterData(string=自定义:字符串5, date=20200106010100, doubleData=600%)
ConverterData(string=自定义:字符串6, date=20200107010100, doubleData=700%)
ConverterData(string=自定义:字符串7, date=20200108010100, doubleData=800%)
ConverterData(string=自定义:字符串8, date=20200109010100, doubleData=900%)
ConverterData(string=自定义:字符串9, date=20200110010100, 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";
    }
```
正文到此结束