EasyExcel保姆级教程(1)
由于博猪所在行业使用到关于Excel导入、导出的功能比较多,本文主要详细介绍一下博猪使用的阿里巴巴的easyExcel
,让Excel相关操作没那么多烦恼。
前言
Excel的缺点
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便
虽然POI是目前使用最多的用来做excel解析的框架,但这个框架并不那么完美。大部分使用POI都是使用他的userModel模式。userModel的好处是上手容易使用简单,随便拷贝个代码跑一下,剩下就是写业务转换了,虽然转换也要写上百行代码,相对比较好理解。然而userModel模式最大的问题是在于非常大的内存消耗,一个几兆的文件解析要用掉上百兆的内存。现在很多应用采用这种模式,之所以还正常在跑一定是并发不大,并发上来后一定会OOM或者频繁的full gc
对POI有过深入了解的估计才知道原来POI还有SAX模式。但SAX模式相对比较复杂,excel有03和07两种版本,两个版本数据存储方式截然不同,sax解析方式也各不一样。想要了解清楚这两种解析方式,才去写代码测试,估计两天时间是需要的。再加上即使解析完,要转换到自己业务模型还要很多繁琐的代码。总体下来感觉至少需要三天,由于代码复杂,后续维护成本巨大。
EasyExcel简介
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。
阿里巴巴GitHub地址:Github
EasyExcelDemo
项目准备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.6</version> </dependency>
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> <optional>true</optional> </dependency>
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency>
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public class FileUtil {
public static InputStream getResourcesFileInputStream(String fileName) { return Thread.currentThread().getContextClassLoader().getResourceAsStream("" + fileName); }
public static String getPath() { return FileUtil.class.getResource("/").getPath(); }
public static File createNewFile(String pathName) { File file = new File(getPath() + pathName); if (file.exists()) { file.delete(); } else { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } } return file; }
public static File readFile(String pathName) { return new File(getPath() + pathName); }
public static File readUserHomeFile(String pathName) { return new File(System.getProperty("user.home") + File.separator + pathName); } }
|
Excel操作-读
测试类相关常量如下:
1 2
| private final String SUFFIX_EXCEL_FILE_TYPE = ".xlsx"; private final String READ_ROOT_RESOURCE = "read";
|
最简单的读
excel示例
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Getter @Setter @ToString public class AnnotationReadEntity {
@ExcelProperty("字符串标题") private String title; @ExcelProperty("日期标题") private Date dateTitle;
@ExcelProperty(index = 2) private Double numberTitle; }
|
监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| public class SimpleReadListener extends AnalysisEventListener<SimpleReadEntity> {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleReadListener.class);
private static final int BATCH_COUNT = 5; List<SimpleReadEntity> saveList = new ArrayList<SimpleReadEntity>();
private EntityDao entityDao; public SimpleReadListener() { this.entityDao = new EntityDao(); }
public SimpleReadListener(EntityDao demoDAO) { this.entityDao = demoDAO; }
public void invoke(SimpleReadEntity simpleReadEntity, AnalysisContext analysisContext) { LOGGER.info("解析到一条数据:{}", JSONObject.toJSONString(simpleReadEntity)); saveList.add(simpleReadEntity); if (saveList.size() >= BATCH_COUNT) { batchSave(); saveList.clear(); } }
@Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { batchSave(); LOGGER.info("所有数据解析完成!"); }
private void batchSave() { if (!saveList.isEmpty()) { LOGGER.info("{}条数据,开始存储数据库!", saveList.size()); entityDao.batchSave(saveList); LOGGER.info("存储数据库成功!"); } } }
|
持久层
1 2 3 4 5 6 7 8
| public class EntityDao<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityDao.class);
public void batchSave(List<T> list) { LOGGER.info(">>>>>>>>>>>>>>EntityDao.batchSave:{}" + JSONObject.toJSONString(list)); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@Test public void testSimpleRead1() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet().doRead(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Test public void testSimpleRead2() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).build(); ReadSheet readSheet = EasyExcel.readSheet(0).build(); excelReader.read(readSheet); } finally { if (excelReader != null) { excelReader.finish(); } } }
|
根据注解读取
excel示例
参照:excel示例
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Getter @Setter @ToString public class AnnotationReadEntity {
@ExcelProperty("字符串标题") private String title; @ExcelProperty("日期标题") private Date dateTitle;
@ExcelProperty(index = 2) private Double numberTitle; }
|
监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| public class AnnotationReadListener extends AnalysisEventListener<AnnotationReadEntity> {
private static final Logger LOGGER = LoggerFactory.getLogger(AnnotationReadListener.class);
private static final int BATCH_COUNT = 5; List<AnnotationReadEntity> saveList = new ArrayList<AnnotationReadEntity>();
private EntityDao entityDao; public AnnotationReadListener() { this.entityDao = new EntityDao(); }
public AnnotationReadListener(EntityDao demoDAO) { this.entityDao = demoDAO; }
public void invoke(AnnotationReadEntity entity, AnalysisContext analysisContext) { LOGGER.info("解析到一条数据:", entity); saveList.add(entity); if (saveList.size() >= BATCH_COUNT) { batchSave(); saveList.clear(); } }
public void doAfterAllAnalysed(AnalysisContext analysisContext) { batchSave(); LOGGER.info("所有数据解析完成!"); }
private void batchSave() { if (!saveList.isEmpty()) { LOGGER.info("{}条数据,开始存储数据库!", saveList.size()); entityDao.batchSave(saveList); LOGGER.info("存储数据库成功!"); } } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Test public void testAnnotationRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "annotationRead" + SUFFIX_EXCEL_FILE_TYPE; ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName, AnnotationReadEntity.class, new AnnotationReadListener()).build(); ReadSheet readSheet = EasyExcel.readSheet(0).build(); excelReader.read(readSheet); } finally { if (excelReader != null) { excelReader.finish(); } } }
|
读多个sheet
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
@Test public void testRepeatedRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).doReadAll();
fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; ExcelReader excelReader = null; try { excelReader = EasyExcel.read(fileName).build();
ReadSheet readSheet1 = EasyExcel.readSheet(0).head(SimpleReadEntity.class).registerReadListener(new SimpleReadListener()).build(); ReadSheet readSheet2 = EasyExcel.readSheet(1).head(SimpleReadEntity.class).registerReadListener(new SimpleReadListener()).build(); excelReader.read(readSheet1, readSheet2); } finally { if (excelReader != null) { excelReader.finish(); } } }
|
读取多行
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@Test public void testManyHeaderRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet() .headRowNumber(1).doRead(); }
|
同步返回数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Test public void testSyncResRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; List<SimpleReadEntity> list = EasyExcel.read(fileName).head(SimpleReadEntity.class).sheet().doReadSync(); for (SimpleReadEntity data : list) { System.out.println("读取到数据: " + JSON.toJSONString(data)); } List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync(); for (Map<Integer, String> data : listMap) { System.out.println(JSON.toJSONString(data)); System.out.println("读取到数据: " + JSON.toJSONString(data)); } }
|
读取表头数据
在SimpleReadListener
监听器上增加方法
1 2 3 4 5 6 7 8 9 10
|
@Override public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) { LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Test public void testHeaderRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, SimpleReadEntity.class, new SimpleReadListener()).sheet().doRead(); }
|
异常处理
对象
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Getter @Setter @ToString public class ExceptionReadEntity { private String title;
private String dateTitle; private Double numberTitle; }
|
在SimpleReadListener
监听器上增加方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
@Override public void onException(Exception exception, AnalysisContext context){ LOGGER.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; LOGGER.error("第{}行,第{}列解析异常", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex()); } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@Test public void testSimpleReadOnException() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, ExceptionReadEntity.class,new SimpleReadListener()).sheet().doRead(); }
|
测试不创建对象的读取
监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class NotCreateObjectReadListener extends AnalysisEventListener<Map<Integer, String>> {
private static final Logger LOGGER = LoggerFactory.getLogger(NotCreateObjectReadListener.class);
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 analysisContext) { saveData(); LOGGER.info("所有数据解析完成!"); }
private void saveData() { LOGGER.info("{}条数据,开始存储数据库!", list.size()); LOGGER.info("存储数据库成功!"); } }
|
测试代码
1 2 3 4 5 6
| @Test public void testNotCreateObjectRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, new NotCreateObjectReadListener()).sheet().doRead(); }
|
格式转换
对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Getter @Setter @ToString public class ConverterReadEntity {
@ExcelProperty(converter = CustomStringConverter.class) private String title;
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") private String dateTitle;
@NumberFormat("#.##%") private Double numberTitle; }
|
监听器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| public class ConverterReadListener extends AnalysisEventListener<ConverterReadEntity> {
private static final Logger LOGGER = LoggerFactory.getLogger(ConverterReadListener.class);
private static final int BATCH_COUNT = 5; List<ConverterReadEntity> saveList = new ArrayList<ConverterReadEntity>();
private EntityDao entityDao; public ConverterReadListener() { this.entityDao = new EntityDao(); }
public ConverterReadListener(EntityDao demoDAO) { this.entityDao = demoDAO; }
public void invoke(ConverterReadEntity entity, AnalysisContext analysisContext) { LOGGER.info("解析到一条数据:", entity); saveList.add(entity); if (saveList.size() >= BATCH_COUNT) { batchSave(); saveList.clear(); } }
public void doAfterAllAnalysed(AnalysisContext analysisContext) { batchSave(); LOGGER.info("所有数据解析完成!"); }
private void batchSave() { if (!saveList.isEmpty()) { LOGGER.info("{}条数据,开始存储数据库!", saveList.size()); entityDao.batchSave(saveList); LOGGER.info("存储数据库成功!"); } } }
|
测试代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Test public void testConvertRead() { String fileName = FileUtil.getPath() + READ_ROOT_RESOURCE + File.separator + "simpleRead" + SUFFIX_EXCEL_FILE_TYPE; EasyExcel.read(fileName, ConverterReadEntity.class, new ConverterReadListener()) .sheet().doRead(); }
|