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";
}