EasyExcel 进行excel读写操作
文章目录
- 前言
- 一、依赖
- 二、读取excel指定sheet页数据分批处理
- 1.excel列名对应的映射实体类
- 2.监听类,用于处理读取的excel行数据
- 3.测试
- 三、读取小数据量excel不分批
- 四、数据写入excel
- 1.数据量比较小可以一次性写入
- 2.数据量大,分批多次写入
- 五、excel文件的上传和下载
- 1.下载
- 2. 上传
- 六、分批读取并写入综合示例
前言
EasyExcel是一个专为Java设计的高效Excel处理工具,它旨在帮助开发者在处理大文件时避免内存溢出问题,同时提供简单易用的API来进行Excel的读写操作。相较于Apache POI和jxl等其他库,EasyExcel通过优化的解析算法显著降低了内存消耗,特别是对于07版以上的Excel文件,其内存占用量远低于使用POI SAX模式解析时的需求,几乎消除了内存溢出的风险。此外,EasyExcel对03版Excel的支持则是基于POI的SAX模式并进行了进一步的模型转换封装,提高了使用的便捷性。
easyexcel github
easyexcel 官网简介
easyexcel 读、写、填充示例
一、依赖
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.3</version></dependency>二、读取excel指定sheet页数据分批处理
假设有 excel 如下
1.excel列名对应的映射实体类
使用@ExcelProperty(value = “”)来标识excel列名
importcom.alibaba.excel.annotation.ExcelProperty;importlombok.Data;importjava.math.BigDecimal;importjava.util.Date;@DatapublicclassRowData{/** 企业订单号 */@ExcelProperty(value="企业订单号")privateStringenterpriseOrderNo;/** 所在部门(新)编号 */@ExcelProperty(value="所在部门(新)编号")privateIntegerdepartmentNewNo;/** 乘车人姓名 */@ExcelProperty(value="乘车人姓名")privateStringpassengerName;/** 订单总金额 */@ExcelProperty(value="订单总金额")privateBigDecimaltotalOrderAmount;/** 企业实付金额 */@ExcelProperty(value="企业实付金额")privateBigDecimalenterpriseActualPayment;/** 订单类型 */@ExcelProperty(value="订单类型")privateStringorderType;/** 下单时间 */@ExcelProperty(value="下单时间")privateDateorderTime;}2.监听类,用于处理读取的excel行数据
importcom.alibaba.excel.context.AnalysisContext;importcom.alibaba.excel.read.listener.ReadListener;importcom.alibaba.excel.util.ListUtils;importcom.alibaba.fastjson2.JSON;importhai.tang.model.RowData;importjava.util.List;// 有个很重要的点 RowDataListener 不能被spring管理,所以如果使用spring要每次读取excel都要new,然后里面用到spring可以构造方法传进去publicclassRowDataListenerimplementsReadListener<RowData>{/** * 每1000条存储数据库或者进行其他操作,然后清理list ,方便内存回收 */privatestaticfinalintBATCH_COUNT=1000;/** * 缓存的数据 */privateList<RowData>cachedDataList=ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);/** * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。 */// private DemoDAO demoDAO;// public RowDataListener() {// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数// demoDAO = new DemoDAO();// }/** * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来 * * @param demoDAO */// public RowDataListener(DemoDAO demoDAO) {// this.demoDAO = demoDAO;// }/** * 这个每一条数据解析都会来调用 * * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */@Overridepublicvoidinvoke(RowDatadata,AnalysisContextcontext){System.out.println("解析到一条数据:{}"+JSON.toJSONString(data));cachedDataList.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif(cachedDataList.size()>=BATCH_COUNT){saveData();// 存储完成清理 listcachedDataList=ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}/** * 所有数据解析完成了 都会来调用 * * @param context */@OverridepublicvoiddoAfterAllAnalysed(AnalysisContextcontext){// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();System.out.println("所有数据解析完成!");}/** * 存储到数据库,或者可改为对数据进行其他操作 */privatevoidsaveData(){System.out.println("{}条数据,开始存储数据库!"+cachedDataList.size());//demoDAO.save(cachedDataList);System.out.println("存储数据库成功!");}}3.测试
StringfileName="C:\\Users\\LAPtOP\\Desktop\\工作簿1.xlsx";// 这里 需要指定读用哪个class去读,然后读取"用车订单"sheet,默认第一行是表头,会默认从第二行开始读。结束后文件流会自动关闭EasyExcel.read(fileName,RowData.class,newRowDataListener()).sheet("用车订单").doRead();//如果前3行是表头,要从第4行开始读取,可以使用headRowNumber(3)//EasyExcel.read(fileName, RowData.class, new RowDataListener()).sheet("用车订单").headRowNumber(3).doRead();三、读取小数据量excel不分批
如果上传的excel文件只有几千行,数据量不大,可以不分批读取。
下面的示例,读取excel文件获取第一个sheet页里,从第4行开始的数据行(前3行是表头)。
Controller层:
@RestController@RequestMapping(value="/apiList")@Api(tags="接口列表")@Slf4jpublicclassUsercontroller{@AutowiredUserConsumeruserConsumer;@ApiOperation("excel文件读取")@GetMapping(value="/v1/readExcel",consumes=MediaType.MULTIPART_FORM_DATA_VALUE)publicvoidreadExcel(@RequestParam(value="file")MultipartFilefile)throwsIOException{userConsumer.readExcel(file);}}excel字段映射类:
/** *excel里的列名映射 */@Data@ExcelIgnoreUnannotatedpublicclassExcelRowModel{@ExcelProperty("姓名")privateStringname;@ExcelProperty("性别")privateStringgender;@ExcelProperty("分数")privateStringscore;}excel监听类:
importcom.alibaba.excel.context.AnalysisContext;importcom.alibaba.excel.event.AnalysisEventListener;importcom.xiaohaitang.somedemo.model.ExcelRowModel;importlombok.extern.slf4j.Slf4j;importjava.util.ArrayList;importjava.util.List;@Slf4jpublicclassUploadExcelListenerextendsAnalysisEventListener<ExcelRowModel>{privateList<ExcelRowModel>dtoList=newArrayList<>();publicList<ExcelRowModel>getList(){returndtoList;}@Overridepublicvoidinvoke(ExcelRowModelconfigsDto,AnalysisContextanalysisContext){dtoList.add(configsDto);}@OverridepublicvoiddoAfterAllAnalysed(AnalysisContextanalysisContext){log.info("EXCECL明细信息sheet页读取完成, 读取到的明细信息条数: {}",dtoList.size());}}service类:
importcom.alibaba.excel.EasyExcelFactory;importcom.alibaba.excel.ExcelReader;importcom.alibaba.excel.read.metadata.ReadSheet;importcom.xiaohaitang.somedemo.model.ExcelRowModel;importcom.xiaohaitang.somedemo.utils.JsonUtils;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Service;importorg.springframework.web.multipart.MultipartFile;importjava.io.IOException;importjava.util.List;@Slf4j@ServicepublicclassUserConsumer{publicvoidreadExcel(MultipartFilefiles)throwsIOException{List<ExcelRowModel>excelRows=parseSelfDrivingFromExcel(files);//处理excel里读取到的数据行 excelRows//todo}//解析获取上传的excel文件privateList<ExcelRowModel>parseSelfDrivingFromExcel(MultipartFileexcel)throwsIOException{UploadExcelListenerlistener=newUploadExcelListener();ExcelReaderexcelReader=EasyExcelFactory.read(excel.getInputStream()).build();//获取第一个sheet页里,从第4行开始的数据ReadSheetbatchInfoSheet=EasyExcelFactory.readSheet(0).head(ExcelRowModel.class).headRowNumber(3).registerReadListener(listener).build();excelReader.read(batchInfoSheet);returnlistener.getList();}}四、数据写入excel
1.数据量比较小可以一次性写入
StringfileName="C:\\Users\\LAPtOP\\Desktop\\工作簿1.xlsx";//从数据库查询出的数据,或者构造的数据List<RowData>list=...;//如果数据只有一万行左右,可以一次性写入。将数据写入 "用车订单" sheet 页EasyExcel.write(fileName,RowData.class).sheet("用车订单").doWrite(list);2.数据量大,分批多次写入
StringfileName="C:\\Users\\LAPtOP\\Desktop\\工作簿1.xlsx";try(ExcelWriterexcelWriter=EasyExcel.write(fileName,RowData.class).build()){// 这里注意 如果同一个sheet只要创建一次WriteSheetwriteSheet=EasyExcel.writerSheet("用车订单").build();// 这里调用了五次,每次获得一批数据写入。实际使用时根据数据库分页的总的页数来for(inti=0;i<5;i++){// 分页去数据库查询数据或者构建数据 这里可以去数据库查询每一页的数据List<RowData>list=...;excelWriter.write(list,writeSheet);}}五、excel文件的上传和下载
1.下载
/** * 文件下载(失败了会返回一个有部分数据的Excel) * <p> * 1. 创建excel对应的实体对象 参照{@link DownloadData} * <p> * 2. 设置返回的 参数 * <p> * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */@GetMapping("download")publicvoiddownload(HttpServletResponseresponse)throwsIOException{// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系StringfileName=URLEncoder.encode("测试","UTF-8").replaceAll("\\+","%20");response.setHeader("Content-disposition","attachment;filename*=utf-8''"+fileName+".xlsx");List<RowData>list=...;EasyExcel.write(response.getOutputStream(),RowData.class).sheet("sheet").doWrite(list);}2. 上传
/** * 文件上传 * <p>1. 创建excel对应的实体对象 参照{@link UploadData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} * <p>3. 直接读即可 */@PostMapping("upload")@ResponseBodypublicStringupload(MultipartFilefile)throwsIOException{EasyExcel.read(file.getInputStream(),RowData.class,newUploadDataListener(uploadDAO)).sheet().doRead();return"success";}六、分批读取并写入综合示例
下面的代码会读取excel文件里的指定sheet页,然后读取指定列的数据,最后新建一个excel文件,把读取到的列数据写入到新建的excel文件的指定sheet里。
importcom.alibaba.excel.ExcelWriter;importcom.alibaba.excel.context.AnalysisContext;importcom.alibaba.excel.read.listener.ReadListener;importcom.alibaba.excel.util.ListUtils;importcom.alibaba.excel.write.metadata.WriteSheet;importcom.alibaba.excel.write.metadata.style.WriteCellStyle;importcom.alibaba.excel.write.style.HorizontalCellStyleStrategy;importorg.apache.poi.ss.usermodel.HorizontalAlignment;importorg.apache.poi.ss.usermodel.VerticalAlignment;importjava.util.List;// 有个很重要的点 RowDataListener 不能被spring管理,所以如果使用spring要每次读取excel都要new,然后里面用到spring可以构造方法传进去publicclassRowDataListenerimplementsReadListener<DiDiReconcileExcelParseModel>{/** * 每读取1000条数据进行操作,然后清理list ,方便内存回收 */privatestaticfinalintBATCH_COUNT=1000;/** * 缓存的数据 */privateList<DiDiReconcileExcelParseModel>cachedDataList=ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);/** * 用于写入excel */privateExcelWriterexcelWriter=null;/** * 用于写入excel */privateWriteSheetwriteSheet=null;publicRowDataListener(ExcelWriterexcelWriter,WriteSheetwriteSheet){this.excelWriter=excelWriter;this.writeSheet=writeSheet;}/** * 获取表头和其他行的水平和垂直居中设置 */publicstaticHorizontalCellStyleStrategygetHorizontalCellStyleStrategy(){// 设置表头单元格样式WriteCellStyleheadWriteCellStyle=newWriteCellStyle();// 设置水平居中headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 设置垂直居中headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 设置除表头外的其他行的内容单元格样式WriteCellStylecontentWriteCellStyle=newWriteCellStyle();// 设置水平居中contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// 设置垂直居中contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// 使用自定义样式策略returnnewHorizontalCellStyleStrategy(headWriteCellStyle,contentWriteCellStyle);}/** * 每一条数据解析都会调用该方法一次 * * @param data one row value. Is is same as * @param context */@Overridepublicvoidinvoke(DiDiReconcileExcelParseModeldata,AnalysisContextcontext){//System.out.println("解析到一条数据:" + JSON.toJSONString(data));cachedDataList.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif(cachedDataList.size()>=BATCH_COUNT){saveData();// 存储完成清理 listcachedDataList=ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}/** * 当所有数据全部解析读取完后 会调用该方法一次 * * @param context */@OverridepublicvoiddoAfterAllAnalysed(AnalysisContextcontext){// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();System.out.println("所有数据写入完成!");excelWriter.finish();if(excelWriter!=null){excelWriter.close();}}/** * 保存数据 */privatevoidsaveData(){System.out.println("写入"+cachedDataList.size()+"条数据");excelWriter.write(cachedDataList,writeSheet);}}测试:
StringwriteFile="C:\\Users\\LEPLOP\\Desktop\\写入.xlsx";ExcelWriterexcelWriter=EasyExcel.write(writeFile,DiDiReconcileExcelParseModel.class).build();WriteSheetwriteSheet=EasyExcel.writerSheet("用车订单")//设置垂直和水平居中.registerWriteHandler(RowDataListener.getHorizontalCellStyleStrategy()).build();//从 readFile 中的 "用车订单" sheet中读取指定的列数据(DiDiReconcileExcelParseModel 里指定的列),然后通过excelWriter, writeSheet 写入到 writeFile中StringreadFile="C:\\Users\\LEPLOP\\Desktop\\test\\账单.xlsx";EasyExcel.read(readFile,DiDiReconcileExcelParseModel.class,newRowDataListener(excelWriter,writeSheet)).sheet("用车订单").doRead();根据测试,读取一个14万行并有216列的excel文件,然后挑选其中6列写入新的excel文件,大概耗时不到3分钟