EasyExcel——读Excel

easyExcel是阿里巴巴开源的一个excel处理框架,以使用简单、节省内存著称。
64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)
可有效避免OOM。
致敬阿里:

---参照官方文档进行编辑,主要记录了工作中用到的,用的少的就没有记录
---官方文档 : https://www.yuque.com/easyexcel
---官方github : https://github.com/alibaba/easyexcel

操作实体类

@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String stringData;
    @ExcelProperty(value = "数字标题")
    private Integer  integerData;
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty("日期标题")
    private Date  dateData;
    @NumberFormat("#.##%")
    @ExcelProperty("浮点型标题")
    private Double   dounleData;
}

监听器类

//DemoDataListener 不能被spring管理,要每次读取excel都要new,因此注入其他类的时候需要通过构造方法进行,而不是通过容器
public class DemoDataListener  extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoDAO demoDAO;
    public DemoDataListener() {
        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
        demoDAO = new DemoDAO();
    }
    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /*
     *  每解析一条数据就会来调用
     */
    @Override
    public void invoke(DemoData demoData, AnalysisContext analysisContext) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(demoData));
        //根据demoData对数据进行校验
        list.add(demoData);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        LOGGER.info("所有数据解析完成!");
    }
    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        demoDAO.save(list);
        LOGGER.info("存储数据库成功!");
    }

}

dao层(逗你玩~~)

/**
 * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。
 **/
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入
    }
}

读示例

        String fileName = "c://***";
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); 
2020-04-13 19:26:37.109  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.111  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.111  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.112  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.112  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.112  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 5条数据,开始存储数据库!
2020-04-13 19:26:37.112  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 存储数据库成功!
2020-04-13 19:26:37.113  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.113  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.113  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.114  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.114  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.114  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 5条数据,开始存储数据库!
2020-04-13 19:26:37.114  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 存储数据库成功!
2020-04-13 19:26:37.114  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 解析到一条数据:{}
2020-04-13 19:26:37.115  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 1条数据,开始存储数据库!
2020-04-13 19:26:37.115  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 存储数据库成功!
2020-04-13 19:26:37.115  INFO 17972 --- [           main] com.lh.easyexcel.util.DemoDataListener   : 所有数据解析完成!

指定列的下标或者列名

    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;

读多个sheet

        String fileName = "c://***";
        // 读取全部sheet
        // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();
        // 读取部分sheet
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        ExcelReader excelReader = EasyExcel.read(fileName).build();
        // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
        ReadSheet readSheet1 =
            EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
        ReadSheet readSheet2 =
            EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
        // 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能
        excelReader.read(readSheet1, readSheet2);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();

日期、数字或者自定义格式转换

    /**
     * 自定义 转换器,不管数据库传过来什么 。给他加上“自定义:”
     */
    @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 cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }
    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
}

不创建对象的读

/**
 * 直接用map接收数据
 *
 * @author Jiaju Zhuang
 */
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {
    private static final Logger LOGGER = LoggerFactory.getLogger(NoModelDataListener.class);
    /**
     * 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;
    List<Map<Integer, String>> list = new ArrayList<Map<Integer, String>>();

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
        LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        if (list.size() >= BATCH_COUNT) {
            saveData();
            list.clear();
        }
    }

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

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        LOGGER.info("存储数据库成功!");
    }
}
    /**
     * 不创建对象的读
     */
    @Test
    public void noModelRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 只要,然后读取第一个sheet 同步读取会自动finish
        EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead();
    }

web中的读

    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();
        return "success";
    }
posted @ 2020-04-13 20:06  6。  阅读(561)  评论(0编辑  收藏  举报