前言
在我们的项目需求中,经常会遇到导出的需求,其中excel的导出最为常见。生成Excel比较有名的框架有Apache poi,jxl等,但他们都存在一个严重的问题就是非常的耗内存,如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.
一、EasyExcel特点
EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,
64M内存1分钟内读取75M(46W行25列)的Excel(当然还有急速模式能更快,但是内存占用会在100M多一点)。
EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。
不支持的功能
单个文件的并发写入、
2、读取读取图片
3、宏
4、csv读取(这个后续可能会考虑)
三、常见问题
读取文件务必使用2.0.5+(现在项目中用的是2.2.10)
2、读写反射对象用到了Cglib动态代理,所以成员变量必须符合驼峰规范,而且使用@Data不能使用@Accessors(chain = true)。后续会考虑支持非驼峰。
3、出现 NoSuchMethodException, ClassNotFoundException, NoClassDefFoundError。极大概率是jar冲突,建议clean项目,或者统一poi 的版本,理论上来说easyexcel兼容poi的3.17,4.0.1,4.1.0所有较新版本
4、用String去接收数字,出现小数点等情况这个是BUG,但是很难修复,后续版本会修复这个问题。目前请使用@NumberFormat注解,里面的参数就是调用了java自带的NumberFormat.format方法,不知道怎么入参的可以自己网上查询。
easyExcel的官方文档地址:https://alibaba-easyexcel.github.io/index.html
四、常用注解
4-1、读
ExcelProperty 指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写,默认第一个字段就是index=0,以此类推。千万注意,要么全部不写,要么全部用index,要么全部用名字去匹配。千万别三个混着用,除非你非常了解源代码中三个混着用怎么去排序的。
@Getter@Setter@EqualsAndHashCode public class IndexOrNameData{//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*@ExcelProperty(index = 2)private Double doubleDataprivate Stringstring;@ExcelProperty ("日期") private Date date;}
ExcelIgnore 默认所有字段都会和excel去匹配,加了这个注解会忽略该字段
//强制读取第三个这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去山配*@ExcelIgnoreprivate Double doubleData
DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。里面的value参照java.text.SimpleDateFormat。
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")private String date;
NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。里面的value参照java.text.DecimalFormat。
@NumberFormat("#.##%") private String doubleData; //接收百比的数字
4-2、写
ExcelProperty index 指定写到第几列,默认根据成员变量排序。value指定写入的名称,默认成员变量的名字,多个value可以参照快速开始中的复杂头
ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段
DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat
NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat
ExcelIgnoreUnannotated 默认不加ExcelProperty 的注解的都会参与读写,加了不会参与
五、EasyExcel的使用
1、依赖
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>2.2.10</version></dependency>
2、读excel
2.1最简单的
对象
@Datapublic class DemoData { private String string; private Date date; private Double doubleData;}
controller类
@PostMapping("/outstoragcExce1")@Apioperation("读取出库excel表")public DeviceResponse storageservice(@RequestBody MultipartFile file) {try{storageservice.storageservice(file);} catch (Exception e){return new DeviceResponse(Constant.FAIL CODE,"出库导失败");}return new DeviceResponse(Constant.SUCCESS CODE,"出库导入成功");}
service实现类
@Autowiredprivate StorageService storageServicel@overridepublil void slorageservice(MulliparlFile file) { Tnnutstream is = null: try{ is=file.getInputstream(); } catch (IDException e){ e.printstackTrace(); } //1.进行读取数数据,slorageReLrieval是我的puju类, //2.new Soragelistenpr(storagpServire)这个是监听器,主要用来i取数据的,别急后面会讲 //3.特别注意的是storageservice这个service,我上面有注入进去 @Autowired,切记不要new会报错 EasyExcel.read(is,StorageRetrieval.class, new Soragelisterer(storageService))sheet().doRead();}
SorageListener监听器
@Componentpublic class SorageListener extends AnalysisEventListener<pojo类> { private static final Logger LOGGER = LoggerFactory.getLogger(SorageListener.class); //读取数据初始化值 private static final int BATCH_COUNT = 50; List<pojo类> list = new ArrayList<pojo类>(); private StorageService storageService; public SorageListener() { storageService=new StorageServiceImpl(); } public SorageListener(StorageService storageService) { this.storageService = storageService; } @Override public void invoke(StorageRetrieval data, AnalysisContext context) { list.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (list.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list list.clear(); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { if(list.size()==0){ return; } saveData(); LOGGER.info("所有数据解析完成!"); } public void saveData() { storageService.save(list); //代码实现类层保存数据 LOGGER.info("存储数据库成功!"); }}
2.2、指定列的下标或者列名
@Datapublic class IndexOrNameData { @ExcelProperty(index = 2) private Double doubleData; @ExcelProperty("字符串") private String string; @ExcelProperty("日期") private Date date;}@Testpublic void indexOrNameRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里默认读取第一个sheet EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();}
3、写excel
3.1最简单的
实体对象
@Data@piModel(value = "年龄统计实体类”public class FToWAgeStatisticalVo implements Serializable {private static final long serialVersionUID = -7891558029837989473L;@ApiModelProperty("区间")@ExcelProperty(value = "区间")private String ageGap;@ApiModeLProperty("病例数”)@ExcelProperty(value ="病例数)private Integer casesNumber ;@ApiModeProperty("密接数”)@ExceProperty(value = "密接数”)private Integer closeNumber ;}
service实现
@Overridepublic void avestatisticalExcel(Httpservlethesponse resonse,FlowReionStatisticalParam flowReionStatisticalParam) throws Exception{ //这里文件名如果涉及中文一定要使用URL编码,否则会乱码 String fileName = URLEncoder.encode( s: "floWAgeStatistical.xlsx" StandardCharsets.UTF_8.toString()); List<FloWAgeStatisticalVo> data = ageStatistical(flowRegionStatisticalParam); response.setContentType("application/force-download"); response.setcharacterEncoding("utf-8"); response.setHeader( s: "Content-Disposition", s1: "attachment;filename=" + fileName); EasyExcel.write(response.getoutputstream(),FLoWAgeStatisticalVo.class) .autoclosestream(true) .exceType(ExcelTypeEnum.XLSX) .sheet( sheetName: "年龄统计表") .doWrite(data) ;}
3.2、列宽、行高
@Data@ContentRowHeight(10)@HeadRowHeight(20)@ColumnWidth(25)public class WidthAndHeightData { @ExcelProperty("字符串") private String string; @ExcelProperty("日期") private Date date; @ColumnWidth(50) @ExcelProperty("数字") private Double doubleData;}
3.3、合并单元格
@Getter@Setter@EqualsAndHashCode// 将第6-7行的2-3列合并成一个单元格// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)public class DemoMergeData { // 这一列 每隔2行 合并单元格 @ContentLoopMerge(eachRow = 2) @ExcelProperty("字符串") private String string; @ExcelProperty("日期") private Date date; @ExcelProperty("数字") private Double doubleData;}
@Test public void mergeWrite() { String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写 LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0); // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板") .doWrite(data()); }
3.4、复杂头写入
@Data@ApiModel("学校学生缺勤信息")public class SchoolAnalyseVo { @ApiModelProperty("学校Id") @ExcelIgnore() private Long schoolId; @ExcelProperty("学校") private String schoolName; @ExcelProperty("学校类型") private String schoolType; .......... @ExcelProperty({"症状", "发热"}) private String fever; @ExcelProperty({"症状", "咳嗽"}) private String cough; @ExcelProperty({"症状", "头痛"}) private String headache; ......... @ExcelProperty({"疾病","普通感冒", "人数"}) private String commonColdNumber ; @ExcelProperty({"疾病","普通感冒", "因病缺勤率"}) private String commonColdRate ; @ExcelProperty({"疾病","流感", "人数"}) private String influenzaNumber; .........}
3.5、日期、数字或者自定义格式转换
@Datapublic class ConverterData { @ExcelProperty(value = "字符串", converter = CustomStringStringConverter.class) private String string; @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") @ExcelProperty("日期") private Date date; @NumberFormat("#.##%") @ExcelProperty(value = "数字") private Double doubleData;}
自定义转换器
public class CustomStringStringConverter implements Converter<String> { @Override public Class supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return "自定义:" + cellData.getStringValue(); } @Override public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return new CellData(value); }}
3.6、指定写入列
@Getter@Setter@EqualsAndHashCodepublic class IndexData { @ExcelProperty(value = "字符串", index = 0) private String string; @ExcelProperty(value = "日期", index = 1) private Date date; @ExcelProperty(value = "数字", index = 3) private Double doubleData;}
3.7、其他读操作
https://www.yuque.com/easyexcel/doc/write
4、填充excel
4.1 最简单的填充
对象
@Getter@Setter@EqualsAndHashCodepublic class FillData{private string name;private double number;private Date date;}
代码
@Testpublic void simpleFill() [ // 模板注 用]来表示你要用的变量 如果本来就有””,”]”特殊字符 用””]"代替 String templateFileName =TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx"; // 方案1 根据对象填充 String fileName = TestFileUtil,getPath() + "simpleFill" + System,currentTimeMillis() + ".xlsx", // 这里 会填充到第一个sheet, 然后文件流会自动关闭 FillData fillData = new FillData(); fillData.setName("张一"); fillData.setNumber(5.2); EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData); // 方案2 根据Map填充 fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx" // 这里 会填充到第一个sheet, 然后文件流会自动关闭 Map<string, Object> map = new HashMap<string, Object>(); map.put("name”,"张二"); map .put("number", 5.2) : EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);}
4.2、其他填充
填充列表、复杂的填充、数据量大的复杂填充、横向的填充、多列表组合填充填充
https://www.yuque.com/easyexcel/doc/fill
来源地址:https://blog.csdn.net/weixin_43945397/article/details/129532669