源自官方文档,仅供我个人使用,其他人请移步官方地址:https://easyexcel.opensource.alibaba.com/,官方文档更详细更好!!
新手必读
- 官方网站:https://easyexcel.opensource.alibaba.com/
- github地址:https://github.com/alibaba/easyexcel
- gitee地址:https://gitee.com/easyexcel/easyexcel
EasyExcel
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。
easyexcel重写了poi对07版Excel的解析,一个3M的excel用POI sax解析依然需要100M左右内存,改用easyexcel可以降低到几M,并且再大的excel也不会出现内存溢出;03版依赖POI的sax模式,在上层做了模型转换的封装,让使用者更加简单方便
网站
- 官方网站:https://easyexcel.opensource.alibaba.com/
- github地址:https://github.com/alibaba/easyexcel
- gitee地址:https://gitee.com/easyexcel/easyexcel
16M内存23秒读取75M(46W行25列)的Excel(3.2.1+版本)
当然还有极速模式能更快,但是内存占用会在100M多一点
帮忙点个⭐Star
开源不易,如果觉得EasyExcel对您的工作还是有帮助的话,请帮忙在 的右上角点个⭐Star,您的支持是使EasyExcel变得更好最大的动力。
如何获取帮助
如何获取帮助
维护者
姬朋飞(玉霄)、庄家钜
快速开始
读Excel
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
/** * 最简单的读 * <p>1. 创建excel对应的实体对象 参照{@link DemoData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p>3. 直接读即可 */ @Test public void simpleRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); }
写Excel
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
/** * 最简单的写 * <p>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData} * <p>2. 直接写即可 */ @Test public void simpleWrite() { String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); }
web上传、下载
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
/** * 文件下载(失败了会返回一个有部分数据的Excel) * <p> * 1. 创建excel对应的实体对象 参照{@link DownloadData} * <p> * 2. 设置返回的 参数 * <p> * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); } /** * 文件上传 * <p>1. 创建excel对应的实体对象 参照{@link UploadData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} * <p>3. 直接读即可 */ @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; }
读Excel
示例代码
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
最简单的读
最简单的读的excel示例
最简单的读的对象
@Getter@Setter@EqualsAndHashCodepublic class DemoData { private String string; private Date date; private Double doubleData;}
最简单的读的监听器
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去@Slf4jpublic class DemoDataListener implements ReadListener<DemoData> { /** * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 100; /** * 缓存的数据 */ private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); /** * 假设这个是一个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; } /** * 这个每一条数据解析都会来调用 * * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()} * @param context */ @Override public void invoke(DemoData data, AnalysisContext context) { log.info("解析到一条数据:{}", JSON.toJSONString(data)); cachedDataList.add(data); // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM if (cachedDataList.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } /** * 所有数据解析完成了 都会来调用 * * @param context */ @Override public void doAfterAllAnalysed(AnalysisContext context) { // 这里也要保存数据,确保最后遗留的数据也存储到数据库 saveData(); log.info("所有数据解析完成!"); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据,开始存储数据库!", cachedDataList.size()); demoDAO.save(cachedDataList); log.info("存储数据库成功!"); }}
持久层
/** * 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。 **/public class DemoDAO { public void save(List<DemoData> list) { // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入 }}
代码
/** * 最简单的读 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p> * 3. 直接读即可 */ @Test public void simpleRead() { // 写法1:JDK8+ ,不用额外写一个DemoDataListener // since: 3.0.0-beta1 String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行 // 具体需要返回多少行可以在`PageReadListener`的构造函数设置 EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> { for (DemoData demoData : dataList) { log.info("读取到一条数据{}", JSON.toJSONString(demoData)); } })).sheet().doRead(); // 写法2: // 匿名内部类 不用额外写一个DemoDataListener fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() { /** * 单次缓存的数据量 */ public static final int BATCH_COUNT = 100; /** *临时存储 */ private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); @Override public void invoke(DemoData data, AnalysisContext context) { cachedDataList.add(data); if (cachedDataList.size() >= BATCH_COUNT) { saveData(); // 存储完成清理 list cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据,开始存储数据库!", cachedDataList.size()); log.info("存储数据库成功!"); } }).sheet().doRead(); // 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去 // 写法3: fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); // 写法4 fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 一个文件一个reader try (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) { // 构建一个sheet 这里可以指定名字或者no ReadSheet readSheet = EasyExcel.readSheet(0).build(); // 读取一个sheet excelReader.read(readSheet); } }
指定列的下标或者列名
excel示例
参照:最简单的读的excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class IndexOrNameData { /** * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配 */ @ExcelProperty(index = 2) private Double doubleData; /** * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据 */ @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date;}
监听器
参照:最简单的读的监听器 只是泛型变了而已
代码
/** * 指定列的下标或者列名 * * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener} * <p>3. 直接读即可 */ @Test public void indexOrNameRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里默认读取第一个sheet EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead(); }
读多个sheet
excel示例
参照:最简单的读的excel示例
对象
参照:最简单的读的对象
监听器
参照:最简单的读的监听器
代码
/** * 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p> * 3. 直接读即可 */ @Test public void repeatedRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 读取全部sheet // 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写 EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll(); // 读取部分sheet fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 写法1 try (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); } }
日期、数字或者自定义格式转换
excel示例
参照:最简单的读的excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class ConverterData { /** * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:” */ @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 context * @return */ @Override public String convertToJavaData(ReadConverterContext<?> context) { return "自定义:" + context.getReadCellData().getStringValue(); } /** * 这里是写的时候会调用 不用管 * * @return */ @Override public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) { return new WriteCellData<>(context.getValue()); }}
代码
/** * 日期、数字或者自定义格式转换 * <p> * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()} * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener} * <p>3. 直接读即可 */ @Test public void converterRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener()) // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。 // 如果就想单个字段使用请使用@ExcelProperty 指定converter // .registerConverter(new CustomStringStringConverter()) // 读取sheet .sheet().doRead(); }
多行头
excel示例
参照:最简单的读的excel示例
对象
参照:最简单的读的对象
监听器
参照:最简单的读的监听器
代码
/** * 多行头 * * <p>1. 创建excel对应的实体对象 参照{@link DemoData} * <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener} * <p>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数, * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。 */ @Test public void complexHeaderRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet() // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行 .headRowNumber(1).doRead(); }
同步的返回
excel示例
参照:excel示例
对象
参照:对象
代码
/** * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面 */ @Test public void synchronousRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish List<DemoData> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync(); for (DemoData data : list) { LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish List<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync(); for (Map<Integer, String> data : listMap) { // 返回每条数据的键值对 表示所在的列 和所在列的值 LOGGER.info("读取到数据:{}", JSON.toJSONString(data)); } }
读取表头数据
excel示例
参照:最简单的读的excel示例
对象
参照:最简单的读的对象
监听器
参照:最简单的读的监听器 里面多了一个方法,只要重写invokeHeadMap方法即可
/** * 这里会一行行的返回头 * * @param headMap * @param context */ @Override public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) { log.info("解析到一条头数据:{}", JSON.toJSONString(headMap)); // 如果想转成成 Map<Integer,String> // 方案1: 不要implements ReadListener 而是 extends AnalysisEventListener // 方案2: 调用 ConverterUtils.convertToStringMap(headMap, context) 自动会转换 }
代码
/** * 读取表头数据 * * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} * <p> * 3. 直接读即可 */ @Test public void headerRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead(); }
额外信息(批注、超链接、合并单元格信息读取)
since
2.0.0-beta1
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class DemoExtraData { private String row1; private String row2;}
监听器
参照:最简单的读的监听器 里面多了一个 extra
方法
@Slf4jpublic class DemoExtraListener implements ReadListener<DemoExtraData> { @Override public void invoke(DemoExtraData data, AnalysisContext context) {} @Override public void doAfterAllAnalysed(AnalysisContext context) {} @Override public void extra(CellExtra extra, AnalysisContext context) { log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra)); switch (extra.getType()) { case COMMENT: log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText()); break; case HYPERLINK: if ("Sheet1!A1".equals(extra.getText())) { log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(), extra.getText()); } else if ("Sheet2!A1".equals(extra.getText())) { log.info( "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}," + "内容是:{}", extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), extra.getLastColumnIndex(), extra.getText()); } else { Assert.fail("Unknown hyperlink!"); } break; case MERGE: log.info( "额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}", extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(), extra.getLastColumnIndex()); break; default: } }}
代码
/** * 额外信息(批注、超链接、合并单元格信息读取) * <p> * 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息,所以只能最后通知哪些单元格有哪些额外信息 * * <p> * 1. 创建excel对应的实体对象 参照{@link DemoExtraData} * <p> * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExtraListener} * <p> * 3. 直接读即可 * * @since 2.2.0-beat1 */ @Test public void extraRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "extra.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, DemoExtraData.class, new DemoExtraListener()) // 需要读取批注 默认不读取 .extraRead(CellExtraTypeEnum.COMMENT) // 需要读取超链接 默认不读取 .extraRead(CellExtraTypeEnum.HYPERLINK) // 需要读取合并单元格信息 默认不读取 .extraRead(CellExtraTypeEnum.MERGE).sheet().doRead(); }
读取公式和单元格类型
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class CellDataReadDemoData { private CellData<String> string; // 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是number private CellData<Date> date; private CellData<Double> doubleData; // 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复 private CellData<String> formulaValue;}
监听器
参照:最简单的读的监听器 代码
/** * 读取公式和单元格类型 * * <p> * 1. 创建excel对应的实体对象 参照{@link CellDataReadDemoData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener} * <p> * 3. 直接读即可 * * @since 2.2.0-beat1 */ @Test public void cellDataRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead(); }
数据转换等异常处理
excel示例
参照:最简单的读的excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class ExceptionDemoData { /** * 用日期去接字符串 肯定报错 */ private Date date;}
监听器
参照:最简单的读的监听器 里面多了一个方法,只要重写onException方法即可
/** * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。 * * @param exception * @param context * @throws Exception */ @Override public void onException(Exception exception, AnalysisContext context) { log.error("解析失败,但是继续解析下一行:{}", exception.getMessage()); // 如果是某一个单元格的转换异常 能获取到具体行号 // 如果要获取头的信息 配合invokeHeadMap使用 if (exception instanceof ExcelDataConvertException) { ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception; log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData()); } }
代码
/** * 数据转换等异常处理 * * <p> * 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener} * <p> * 3. 直接读即可 */ @Test public void exceptionRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, ExceptionDemoData.class, new DemoExceptionListener()).sheet().doRead(); }
不创建对象的读
excel示例
参照:最简单的读的excel示例
监听器
@Slf4jpublic class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> { /** * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收 */ private static final int BATCH_COUNT = 5; private List<Map<Integer, String>> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { log.info("解析到一条数据:{}", JSON.toJSONString(data)); cachedDataList.add(data); if (cachedDataList.size() >= BATCH_COUNT) { saveData(); cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { saveData(); log.info("所有数据解析完成!"); } /** * 加上存储数据库 */ private void saveData() { log.info("{}条数据,开始存储数据库!", cachedDataList.size()); log.info("存储数据库成功!"); }}
代码
/** * 不创建对象的读 */ @Test public void noModelRead() { String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; // 这里 只要,然后读取第一个sheet 同步读取会自动finish EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead(); }
web中的读
示例代码
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
excel示例
参照:最简单的读的excel示例
对象
参照:最简单的读的对象 只是名字变了
监听器
参照:最简单的读的监听器 只是泛型变了
代码
/** * 文件上传 * <p> * 1. 创建excel对应的实体对象 参照{@link UploadData} * <p> * 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener} * <p> * 3. 直接读即可 */ @PostMapping("upload") @ResponseBody public String upload(MultipartFile file) throws IOException { EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead(); return "success"; }
写Excel
通用数据生成 后面不会重复写
private List<DemoData> data() { List<DemoData> list = ListUtils.newArrayList(); for (int i = 0; i < 10; i++) { DemoData data = new DemoData(); data.setString("字符串" + i); data.setDate(new Date()); data.setDoubleData(0.56); list.add(data); } return list; }
示例代码
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
最简单的写
excel示例
最简单的写的对象
@Getter@Setter@EqualsAndHashCodepublic class DemoData { @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date; @ExcelProperty("数字标题") private Double doubleData; /** * 忽略这个字段 */ @ExcelIgnore private String ignore;}
代码
/** * 最简单的写 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 直接写即可 */ @Test public void simpleWrite() { // 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入 // 写法1 JDK8+ // since: 3.0.0-beta1 String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class) .sheet("模板") .doWrite(() -> { // 分页查询数据 return data(); }); // 写法2 fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 如果这里想使用03 则 传入excelType参数即可 EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data()); // 写法3 fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); excelWriter.write(data(), writeSheet); } }
根据参数只导出指定列
excel示例
对象
参照:最简单的写的对象
代码
/** * 根据参数只导出指定列 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 根据自己或者排除自己需要的列 * <p> * 3. 直接写即可 * * @since 2.1.1 */ @Test public void excludeOrIncludeWrite() { String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。 // 根据用户传入字段 假设我们要忽略 date Set<String> excludeColumnFiledNames = new HashSet<String>(); excludeColumnFiledNames.add("date"); // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板") .doWrite(data()); fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx"; // 根据用户传入字段 假设我们只要导出 date Set<String> includeColumnFiledNames = new HashSet<String>(); includeColumnFiledNames.add("date"); // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板") .doWrite(data()); }
指定写入的列
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class IndexData { @ExcelProperty(value = "字符串标题", index = 0) private String string; @ExcelProperty(value = "日期标题", index = 1) private Date date; /** * 这里设置3 会导致第二列空的 */ @ExcelProperty(value = "数字标题", index = 3) private Double doubleData;}
代码
/** * 指定写入的列 * <p>1. 创建excel对应的实体对象 参照{@link IndexData} * <p>2. 使用{@link ExcelProperty}注解指定写入的列 * <p>3. 直接写即可 */ @Test public void indexWrite() { String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data()); }
复杂头写入
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class ComplexHeadData { @ExcelProperty({"主标题", "字符串标题"}) private String string; @ExcelProperty({"主标题", "日期标题"}) private Date date; @ExcelProperty({"主标题", "数字标题"}) private Double doubleData;}
代码
/** * 复杂头写入 * <p>1. 创建excel对应的实体对象 参照{@link ComplexHeadData} * <p>2. 使用{@link ExcelProperty}注解指定复杂的头 * <p>3. 直接写即可 */ @Test public void complexHeadWrite() { String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data()); }
重复多次写入(写到单个或者多个Sheet)
excel示例
对象
参照:最简单的写的对象
代码
/** * 重复多次写入 * <p> * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData} * <p> * 2. 使用{@link ExcelProperty}注解指定复杂的头 * <p> * 3. 直接调用二次写入即可 */ @Test public void repeatedWrite() { // 方法1: 如果写到同一个sheet String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { // 这里注意 如果同一个sheet只要创建一次 WriteSheet writeSheet = EasyExcel.writerSheet("模板").build(); // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来 for (int i = 0; i < 5; i++) { // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 List<DemoData> data = data(); excelWriter.write(data, writeSheet); } } // 方法2: 如果写到不同的sheet 同一个对象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 指定文件 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 for (int i = 0; i < 5; i++) { // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样 WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build(); // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 List<DemoData> data = data(); excelWriter.write(data, writeSheet); } } // 方法3 如果写到不同的sheet 不同的对象 fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 指定文件 try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) { // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面 for (int i = 0; i < 5; i++) { // 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class // 实际上可以一直变 WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(DemoData.class).build(); // 分页去数据库查询数据 这里可以去数据库查询每一页的数据 List<DemoData> data = data(); excelWriter.write(data, writeSheet); } } }
日期、数字或者自定义格式转换
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class ConverterData { /** * 我想所有的 字符串起前面加上"自定义:"三个字 */ @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class) private String string; /** * 我想写到excel 用年月日的格式 */ @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") @ExcelProperty("日期标题") private Date date; /** * 我想写到excel 用百分比表示 */ @NumberFormat("#.##%") @ExcelProperty(value = "数字标题") private Double doubleData;}
代码
/** * 日期、数字或者自定义格式转换 * <p>1. 创建excel对应的实体对象 参照{@link ConverterData} * <p>2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解 * <p>3. 直接写即可 */ @Test public void converterWrite() { String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data()); }
图片导出
excel示例
对象
@Getter@Setter@EqualsAndHashCode@ContentRowHeight(100)@ColumnWidth(100 / 8)public class ImageDemoData { private File file; private InputStream inputStream; /** * 如果string类型 必须指定转换器,string默认转换成string */ @ExcelProperty(converter = StringImageConverter.class) private String string; private byte[] byteArray; /** * 根据url导出 * * @since 2.1.1 */ private URL url; /** * 根据文件导出 并设置导出的位置。 * * @since 3.0.0-beta1 */ private WriteCellData<Void> writeCellDataFile;}
代码
/** * 图片导出 * <p> * 1. 创建excel对应的实体对象 参照{@link ImageDemoData} * <p> * 2. 直接写即可 */ @Test public void imageWrite() throws Exception { String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx"; // 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1: // 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接 // 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片 String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg"; try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) { List<ImageDemoData> list = ListUtils.newArrayList(); ImageDemoData imageDemoData = new ImageDemoData(); list.add(imageDemoData); // 放入五种类型的图片 实际使用只要选一种即可 imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath))); imageDemoData.setFile(new File(imagePath)); imageDemoData.setString(imagePath); imageDemoData.setInputStream(inputStream); imageDemoData.setUrl(new URL( "https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg")); // 这里演示 // 需要额外放入文字 // 而且需要放入2个图片 // 第一个图片靠左 // 第二个靠右 而且要额外的占用他后面的单元格 WriteCellData<Void> writeCellData = new WriteCellData<>(); imageDemoData.setWriteCellDataFile(writeCellData); // 这里可以设置为 EMPTY 则代表不需要其他数据了 writeCellData.setType(CellDataTypeEnum.STRING); writeCellData.setStringValue("额外的放一些文字"); // 可以放入多个图片 List<ImageData> imageDataList = new ArrayList<>(); ImageData imageData = new ImageData(); imageDataList.add(imageData); writeCellData.setImageDataList(imageDataList); // 放入2进制图片 imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath))); // 图片类型 imageData.setImageType(ImageType.PICTURE_TYPE_PNG); // 上 右 下 左 需要留空 // 这个类似于 css 的 margin // 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。 imageData.setTop(5); imageData.setRight(40); imageData.setBottom(5); imageData.setLeft(5); // 放入第二个图片 imageData = new ImageData(); imageDataList.add(imageData); writeCellData.setImageDataList(imageDataList); imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath))); imageData.setImageType(ImageType.PICTURE_TYPE_PNG); imageData.setTop(5); imageData.setRight(5); imageData.setBottom(5); imageData.setLeft(50); // 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格 // 起点相对于当前单元格为0 当然可以不写 imageData.setRelativeFirstRowIndex(0); imageData.setRelativeFirstColumnIndex(0); imageData.setRelativeLastRowIndex(0); // 前面3个可以不写 下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格 // 也就是说 这个图片会覆盖当前单元格和 后面的那一格 imageData.setRelativeLastColumnIndex(1); // 写入数据 EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list); } }
超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class WriteCellDemoData { /** * 超链接 * * @since 3.0.0-beta1 */ private WriteCellData<String> hyperlink; /** * 备注 * * @since 3.0.0-beta1 */ private WriteCellData<String> commentData; /** * 公式 * * @since 3.0.0-beta1 */ private WriteCellData<String> formulaData; /** * 指定单元格的样式。当然样式 也可以用注解等方式。 * * @since 3.0.0-beta1 */ private WriteCellData<String> writeCellStyle; /** * 指定一个单元格有多个样式 * * @since 3.0.0-beta1 */ private WriteCellData<String> richText;}
代码
/** * 超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式 * <p> * 1. 创建excel对应的实体对象 参照{@link WriteCellDemoData} * <p> * 2. 直接写即可 * * @since 3.0.0-beta1 */ @Test public void writeCellDataWrite() { String fileName = TestFileUtil.getPath() + "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx"; WriteCellDemoData writeCellDemoData = new WriteCellDemoData(); // 设置超链接 WriteCellData<String> hyperlink = new WriteCellData<>("官方网站"); writeCellDemoData.setHyperlink(hyperlink); HyperlinkData hyperlinkData = new HyperlinkData(); hyperlink.setHyperlinkData(hyperlinkData); hyperlinkData.setAddress("https://github.com/alibaba/easyexcel"); hyperlinkData.setHyperlinkType(HyperlinkType.URL); // 设置备注 WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息"); writeCellDemoData.setCommentData(comment); CommentData commentData = new CommentData(); comment.setCommentData(commentData); commentData.setAuthor("Jiaju Zhuang"); commentData.setRichTextStringData(new RichTextStringData("这是一个备注")); // 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格 commentData.setRelativeLastColumnIndex(1); commentData.setRelativeLastRowIndex(1); // 设置公式 WriteCellData<String> formula = new WriteCellData<>(); writeCellDemoData.setFormulaData(formula); FormulaData formulaData = new FormulaData(); formula.setFormulaData(formulaData); // 将 123456789 中的第一个数字替换成 2 // 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用 formulaData.setFormulaValue("REPLACE(123456789,1,1,2)"); // 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。 WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式"); writeCellStyle.setType(CellDataTypeEnum.STRING); writeCellDemoData.setWriteCellStyle(writeCellStyle); WriteCellStyle writeCellStyleData = new WriteCellStyle(); writeCellStyle.setWriteCellStyle(writeCellStyleData); // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色. writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 背景绿色 writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex()); // 设置单个单元格多种样式 // 这里需要设置 inMomery=true 不然会导致无法展示单个单元格多种样式,所以慎用 WriteCellData<String> richTest = new WriteCellData<>(); richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING); writeCellDemoData.setRichText(richTest); RichTextStringData richTextStringData = new RichTextStringData(); richTest.setRichTextStringDataValue(richTextStringData); richTextStringData.setTextString("红色绿色默认"); // 前2个字红色 WriteFont writeFont = new WriteFont(); writeFont.setColor(IndexedColors.RED.getIndex()); richTextStringData.applyFont(0, 2, writeFont); // 接下来2个字绿色 writeFont = new WriteFont(); writeFont.setColor(IndexedColors.GREEN.getIndex()); richTextStringData.applyFont(2, 4, writeFont); List<WriteCellDemoData> data = new ArrayList<>(); data.add(writeCellDemoData); EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data); }
根据模板写入
模板excel示例
excel示例
对象
参照:最简单的写的对象
代码
/** * 根据模板写入 * <p>1. 创建excel对应的实体对象 参照{@link IndexData} * <p>2. 使用{@link ExcelProperty}注解指定写入的列 * <p>3. 使用withTemplate 写取模板 * <p>4. 直接写即可 */ @Test public void templateWrite() { String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx"; String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM // 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入 EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data()); }
列宽、行高
excel示例
对象
@Getter@Setter@EqualsAndHashCode@ContentRowHeight(10)@HeadRowHeight(20)@ColumnWidth(25)public class WidthAndHeightData { @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date; /** * 宽度为50 */ @ColumnWidth(50) @ExcelProperty("数字标题") private Double doubleData;}
代码
/** * 列宽、行高 * <p>1. 创建excel对应的实体对象 参照{@link WidthAndHeightData} * <p>2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度 * <p>3. 直接写即可 */ @Test public void widthAndHeightWrite() { String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data()); }
注解形式自定义样式
since
2.2.0-beta1
excel示例
对象
@Getter@Setter@EqualsAndHashCode// 头背景设置成红色 IndexedColors.RED.getIndex()@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 10)// 头字体设置成20@HeadFontStyle(fontHeightInPoints = 20)// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()@ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 17)// 内容字体设置成20@ContentFontStyle(fontHeightInPoints = 20)public class DemoStyleData { // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex() @HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 14) // 字符串的头字体设置成20 @HeadFontStyle(fontHeightInPoints = 30) // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex() @ContentStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND, fillForegroundColor = 40) // 字符串的内容字体设置成20 @ContentFontStyle(fontHeightInPoints = 30) @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date; @ExcelProperty("数字标题") private Double doubleData;}/** * 样式的数据类 * * @author Jiaju Zhuang **/@Data// 头背景设置成红色 IndexedColors.RED.getIndex()@HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 10)// 头字体设置成20@HeadFontStyle(fontHeightInPoints = 20)// 内容的背景设置成绿色 IndexedColors.GREEN.getIndex()@ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 17)// 内容字体设置成20@ContentFontStyle(fontHeightInPoints = 20)public class DemoStyleData { // 字符串的头背景设置成粉红 IndexedColors.PINK.getIndex() @HeadStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 14) // 字符串的头字体设置成20 @HeadFontStyle(fontHeightInPoints = 30) // 字符串的内容的背景设置成天蓝 IndexedColors.SKY_BLUE.getIndex() @ContentStyle(fillPatternType = FillPatternType.SOLID_FOREGROUND, fillForegroundColor = 40) // 字符串的内容字体设置成20 @ContentFontStyle(fontHeightInPoints = 30) @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题") private Date date; @ExcelProperty("数字标题") private Double doubleData;}
代码
/** * 注解形式自定义样式 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoStyleData} * <p> * 3. 直接写即可 * * @since 2.2.0-beta1 */ @Test public void annotationStyleWrite() { String fileName = TestFileUtil.getPath() + "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoStyleData.class).sheet("模板").doWrite(data()); }
自定义样式
excel示例
对象
参照:最简单的写的对象
代码
/** * 拦截器形式自定义样式 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 创建一个style策略 并注册 * <p> * 3. 直接写即可 */ @Test public void handlerStyleWrite() { // 方法1 使用已有的策略 推荐 // HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样 // AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页 String fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; // 头的策略 WriteCellStyle headWriteCellStyle = new WriteCellStyle(); // 背景设置为红色 headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); WriteFont headWriteFont = new WriteFont(); headWriteFont.setFontHeightInPoints((short)20); headWriteCellStyle.setWriteFont(headWriteFont); // 内容的策略 WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定 contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 背景绿色 contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex()); WriteFont contentWriteFont = new WriteFont(); // 字体大小 contentWriteFont.setFontHeightInPoints((short)20); contentWriteCellStyle.setWriteFont(contentWriteFont); // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现 HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle); // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class) .registerWriteHandler(horizontalCellStyleStrategy) .sheet("模板") .doWrite(data()); // 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略 // @since 3.0.0-beta2 fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName, DemoData.class) .registerWriteHandler(new CellWriteHandler() { @Override public void afterCellDispose(CellWriteHandlerContext context) { // 当前事件会在 数据设置到poi的cell里面才会回调 // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true if (BooleanUtils.isNotTrue(context.getHead())) { // 第一个单元格 // 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellData WriteCellData<?> cellData = context.getFirstCellData(); // 这里需要去cellData 获取样式 // 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat // ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了 // 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回 WriteCellStyle writeCellStyle = cellData.getOrCreateStyle(); writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND); // 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了 } } }).sheet("模板") .doWrite(data()); // 方法3: 使用poi的样式完全自己写 不推荐 // @since 3.0.0-beta2 // 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效 // 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了 fileName = TestFileUtil.getPath() + "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName, DemoData.class) .registerWriteHandler(new CellWriteHandler() { @Override public void afterCellDispose(CellWriteHandlerContext context) { // 当前事件会在 数据设置到poi的cell里面才会回调 // 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not true if (BooleanUtils.isNotTrue(context.getHead())) { Cell cell = context.getCell(); // 拿到poi的workbook Workbook workbook = context.getWriteWorkbookHolder().getWorkbook(); // 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式 // 不同单元格尽量传同一个 cellStyle CellStyle cellStyle = workbook.createCellStyle(); cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex()); // 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); cell.setCellStyle(cellStyle); // 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确 // 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 // cell里面去 会导致自己设置的不一样 context.getFirstCellData().setWriteCellStyle(null); } } }).sheet("模板") .doWrite(data()); }
合并单元格
since
2.2.0-beta1
excel示例
对象
方法1
@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;}
方法2参照:最简单的写的对象
代码
/** * 合并单元格 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} {@link DemoMergeData} * <p> * 2. 创建一个merge策略 并注册 * <p> * 3. 直接写即可 * * @since 2.2.0-beta1 */ @Test public void mergeWrite() { // 方法1 注解 String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx"; // 在DemoStyleData里面加上ContentLoopMerge注解 // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoMergeData.class).sheet("模板").doWrite(data()); // 方法2 自定义合并单元格策略 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()); }
使用table去写入
excel示例
对象
参照:最简单的写的对象
代码
/** * 使用table去写入 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 然后写入table即可 */ @Test public void tableWrite() { String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx"; // 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案 // 这里 需要指定写用哪个class去写 try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) { // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了 WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build(); // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要 WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build(); WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build(); // 第一次写入会创建头 excelWriter.write(data(), writeSheet, writeTable0); // 第二次写如也会创建头,然后在第一次的后面写入数据 excelWriter.write(data(), writeSheet, writeTable1); } }
动态头,实时生成头写入
excel示例
对象
参照:最简单的写的对象
代码
/** * 动态头,实时生成头写入 * <p> * 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据 * * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 然后写入table即可 */ @Test public void dynamicHeadWrite() { String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx"; EasyExcel.write(fileName) // 这里放入动态头 .head(head()).sheet("模板") // 当然这里数据也可以用 List<List<String>> 去传入 .doWrite(data()); } private List<List<String>> head() { List<List<String>> list = new ArrayList<List<String>>(); List<String> head0 = new ArrayList<String>(); head0.add("字符串" + System.currentTimeMillis()); List<String> head1 = new ArrayList<String>(); head1.add("数字" + System.currentTimeMillis()); List<String> head2 = new ArrayList<String>(); head2.add("日期" + System.currentTimeMillis()); list.add(head0); list.add(head1); list.add(head2); return list; }
自动列宽(不太精确)
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class LongestMatchColumnWidthData { @ExcelProperty("字符串标题") private String string; @ExcelProperty("日期标题很长日期标题很长日期标题很长很长") private Date date; @ExcelProperty("数字") private Double doubleData;}
代码
/** * 自动列宽(不太精确) * <p> * 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 * {@link LongestMatchColumnWidthStyleStrategy}重新实现. * <p> * poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。 * * <p> * 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData} * <p> * 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy} * <p> * 3. 直接写即可 */ @Test public void longestMatchColumnWidthWrite() { String fileName = TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, LongestMatchColumnWidthData.class) .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong()); } private List<LongestMatchColumnWidthData> dataLong() { List<LongestMatchColumnWidthData> list = new ArrayList<LongestMatchColumnWidthData>(); for (int i = 0; i < 10; i++) { LongestMatchColumnWidthData data = new LongestMatchColumnWidthData(); data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i); data.setDate(new Date()); data.setDoubleData(1000000000000.0); list.add(data); } return list; }
自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
excel示例
对象
参照:最简单的写的对象
定义拦截器
/** * 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel * * @author Jiaju Zhuang */@Slf4jpublic class CustomCellWriteHandler implements CellWriteHandler { @Override public void afterCellDispose(CellWriteHandlerContext context) { Cell cell = context.getCell(); // 这里可以对cell进行任何操作 log.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex()); if (BooleanUtils.isTrue(context.getHead()) && cell.getColumnIndex() == 0) { CreationHelper createHelper = context.getWriteSheetHolder().getSheet().getWorkbook().getCreationHelper(); Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL); hyperlink.setAddress("https://github.com/alibaba/easyexcel"); cell.setHyperlink(hyperlink); } }}
/** * 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2 * * @author Jiaju Zhuang */@Slf4jpublic class CustomSheetWriteHandler implements SheetWriteHandler { @Override public void afterSheetCreate(SheetWriteHandlerContext context) { log.info("第{}个Sheet写入成功。", context.getWriteSheetHolder().getSheetNo()); // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行 CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0); DataValidationHelper helper = context.getWriteSheetHolder().getSheet().getDataValidationHelper(); DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"}); DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList); context.getWriteSheetHolder().getSheet().addValidationData(dataValidation); }}
代码
/** * 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个) * <p> * demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler} * <p> * 2. 直接写即可 */ @Test public void customHandlerWrite() { String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler()) .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data()); }
插入批注
excel示例
对象
参照:最简单的写的对象
定义拦截器
/** * 自定义拦截器.新增注释,第一行头加批注 * * @author Jiaju Zhuang */@Slf4jpublic class CommentWriteHandler implements RowWriteHandler { @Override public void afterRowDispose(RowWriteHandlerContext context) { if (BooleanUtils.isTrue(context.getHead())) { Sheet sheet = context.getWriteSheetHolder().getSheet(); Drawing<?> drawingPatriarch = sheet.createDrawingPatriarch(); // 在第一行 第二列创建一个批注 Comment comment = drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short)1, 0, (short)2, 1)); // 输入批注信息 comment.setString(new XSSFRichTextString("创建批注!")); // 将批注添加到单元格对象中 sheet.getRow(0).getCell(1).setCellComment(comment); } }}
代码
/** * 插入批注 * <p> * 1. 创建excel对应的实体对象 参照{@link DemoData} * <p> * 2. 注册拦截器 {@link CommentWriteHandler} * <p> * 2. 直接写即可 */ @Test public void commentWrite() { String fileName = TestFileUtil.getPath() + "commentWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 // 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。 EasyExcel.write(fileName, DemoData.class).inMemory(Boolean.TRUE).registerWriteHandler(new CommentWriteHandler()) .sheet("模板").doWrite(data()); }
可变标题处理(包括标题国际化等)
excel示例
对象
@Getter@Setter@EqualsAndHashCodepublic class ConverterData { /** * 我想所有的 字符串起前面加上"自定义:"三个字 */ @ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class) private String string; /** * 我想写到excel 用年月日的格式 */ @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒") @ExcelProperty("日期标题") private Date date; /** * 我想写到excel 用百分比表示 */ @NumberFormat("#.##%") @ExcelProperty(value = "数字标题") private Double doubleData;}
代码
/** * 可变标题处理(包括标题国际化等) * <p> * 简单的说用List<List<String>>的标题 但是还支持注解 * <p> * 1. 创建excel对应的实体对象 参照{@link ConverterData} * <p> * 2. 直接写即可 */ @Test public void variableTitleWrite() { // 写法1 String fileName = TestFileUtil.getPath() + "variableTitleWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName, ConverterData.class).head(variableTitleHead()).sheet("模板").doWrite(data()); } private List<List<String>> variableTitleHead() { List<List<String>> list = ListUtils.newArrayList(); List<String> head0 = ListUtils.newArrayList(); head0.add("string" + System.currentTimeMillis()); List<String> head1 = ListUtils.newArrayList(); head1.add("number" + System.currentTimeMillis()); List<String> head2 = ListUtils.newArrayList(); head2.add("date" + System.currentTimeMillis()); list.add(head0); list.add(head1); list.add(head2); return list; } private List<List<String>> variableTitleHead() { List<List<String>> list = new ArrayList<>(); List<String> head0 = new ArrayList<>(); head0.add("string" + System.currentTimeMillis()); List<String> head1 = new ArrayList<>(); head1.add("number" + System.currentTimeMillis()); List<String> head2 = new ArrayList<>(); head2.add("date" + System.currentTimeMillis()); list.add(head0); list.add(head1); list.add(head2); return list; }
不创建对象的写
excel示例
代码
/** * 不创建对象的写 */ @Test public void noModelWrite() { // 写法1 String fileName = TestFileUtil.getPath() + "noModelWrite" + System.currentTimeMillis() + ".xlsx"; // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭 EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList()); } private List<List<String>> head() { List<List<String>> list = ListUtils.newArrayList(); List<String> head0 = ListUtils.newArrayList(); head0.add("字符串" + System.currentTimeMillis()); List<String> head1 = ListUtils.newArrayList(); head1.add("数字" + System.currentTimeMillis()); List<String> head2 = ListUtils.newArrayList(); head2.add("日期" + System.currentTimeMillis()); list.add(head0); list.add(head1); list.add(head2); return list; } private List<List<Object>> dataList() { List<List<Object>> list = ListUtils.newArrayList(); for (int i = 0; i < 10; i++) { List<Object> data = ListUtils.newArrayList(); data.add("字符串" + i); data.add(0.56); data.add(new Date()); list.add(data); } return list; }
web中的写
示例代码
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
对象
参照:最简单的写的对象 就是名称变了下
代码
/** * 文件下载(失败了会返回一个有部分数据的Excel) * <p> * 1. 创建excel对应的实体对象 参照{@link DownloadData} * <p> * 2. 设置返回的 参数 * <p> * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大 */ @GetMapping("download") public void download(HttpServletResponse response) throws IOException { // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); }
web中的写并且失败的时候返回json
since
2.1.1
对象
参照:最简单的写的对象 就是名称变了下
代码
/** * 文件下载并且失败的时候返回json(默认失败了会返回一个有部分数据的Excel) * * @since 2.1.1 */ @GetMapping("downloadFailedUsingJson") public void downloadFailedUsingJson(HttpServletResponse response) throws IOException { // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman try { response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); // 这里需要设置不关闭流 EasyExcel.write(response.getOutputStream(), DownloadData.class).autoCloseStream(Boolean.FALSE).sheet("模板") .doWrite(data()); } catch (Exception e) { // 重置response response.reset(); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); Map<String, String> map = MapUtils.newHashMap(); map.put("status", "failure"); map.put("message", "下载文件失败" + e.getMessage()); response.getWriter().println(JSON.toJSONString(map)); } }
填充Excel
示例代码
DEMO代码地址:https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/fill/FillTest.java
最简单的填充
since
2.1.1
最简单的填充的模板
最终效果
最简单的填充的对象
@Getter@Setter@EqualsAndHashCodepublic class FillData { private String name; private double number; private Date date;}
代码
/** * 最简单的填充 * * @since 2.1.1 */ @Test public 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 = MapUtils.newHashMap(); map.put("name", "张三"); map.put("number", 5.2); EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map); }
填充列表
since
2.1.1
模板
最终效果
对象
参照:最简单的填充的对象
代码
/** * 填充列表 * * @since 2.1.1 */ @Test public void listFill() { // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 // 填充list 的时候还要注意 模板中{.} 多了个点 表示list // 如果填充list的对象是map,必须包涵所有list的key,哪怕数据为null,必须使用map.put(key,null) String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "list.xlsx"; // 方案1 一下子全部放到内存里面 并填充 String fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; // 这里 会填充到第一个sheet, 然后文件流会自动关闭 EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(data()); // 方案2 分多次 填充 会使用文件缓存(省内存) fileName = TestFileUtil.getPath() + "listFill" + System.currentTimeMillis() + ".xlsx"; try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); excelWriter.fill(data(), writeSheet); excelWriter.fill(data(), writeSheet); } }
复杂的填充
since
2.1.1
模板
最终效果
对象
参照:最简单的填充的对象
代码
/** * 复杂的填充 * * @since 2.1.1 */ @Test public void complexFill() { // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 // {} 代表普通变量 {.} 代表是list的变量 String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complex.xlsx"; String fileName = TestFileUtil.getPath() + "complexFill" + System.currentTimeMillis() + ".xlsx"; // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 这里注意 入参用了forceNewRow 代表在写入list的时候不管list下面有没有空行 都会创建一行,然后下面的数据往后移动。默认 是false,会直接使用下一行,如果没有则创建。 // forceNewRow 如果设置了true,有个缺点 就是他会把所有的数据都放到内存了,所以慎用 // 简单的说 如果你的模板有list,且list不是最后一行,下面还有数据需要填充 就必须设置 forceNewRow=true 但是这个就会把所有数据放到内存 会很耗内存 // 如果数据量大 list不是最后一行 参照下一个 FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build(); excelWriter.fill(data(), fillConfig, writeSheet); excelWriter.fill(data(), fillConfig, writeSheet); Map<String, Object> map = MapUtils.newHashMap(); map.put("date", "2019年10月9日13:28:28"); map.put("total", 1000); excelWriter.fill(map, writeSheet); } }
数据量大的复杂填充
since
2.1.1
模板
最终效果
对象
参照:最简单的填充的对象
代码
/** * 数据量大的复杂填充 * <p> * 这里的解决方案是 确保模板list为最后一行,然后再拼接table.还有03版没救,只能刚正面加内存。 * * @since 2.1.1 */ @Test public void complexFillWithTable() { // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 // {} 代表普通变量 {.} 代表是list的变量 // 这里模板 删除了list以后的数据,也就是统计的这一行 String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "complexFillWithTable.xlsx"; String fileName = TestFileUtil.getPath() + "complexFillWithTable" + System.currentTimeMillis() + ".xlsx"; // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); // 直接写入数据 excelWriter.fill(data(), writeSheet); excelWriter.fill(data(), writeSheet); // 写入list之前的数据 Map<String, Object> map = new HashMap<String, Object>(); map.put("date", "2019年10月9日13:28:28"); excelWriter.fill(map, writeSheet); // list 后面还有个统计 想办法手动写入 // 这里偷懒直接用list 也可以用对象 List<List<String>> totalListList = ListUtils.newArrayList(); List<String> totalList = ListUtils.newArrayList(); totalListList.add(totalList); totalList.add(null); totalList.add(null); totalList.add(null); // 第四列 totalList.add("统计:1000"); // 这里是write 别和fill 搞错了 excelWriter.write(totalListList, writeSheet); // 总体上写法比较复杂 但是也没有想到好的版本 异步的去写入excel 不支持行的删除和移动,也不支持备注这种的写入,所以也排除了可以 // 新建一个 然后一点点复制过来的方案,最后导致list需要新增行的时候,后面的列的数据没法后移,后续会继续想想解决方案 } }
横向的填充
since
2.1.1
模板
最终效果
对象
参照:最简单的填充的对象
代码
/** * 横向的填充 * * @since 2.1.1 */ @Test public void horizontalFill() { // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 // {} 代表普通变量 {.} 代表是list的变量 String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "horizontal.xlsx"; String fileName = TestFileUtil.getPath() + "horizontalFill" + System.currentTimeMillis() + ".xlsx"; // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); excelWriter.fill(data(), fillConfig, writeSheet); excelWriter.fill(data(), fillConfig, writeSheet); Map<String, Object> map = new HashMap<>(); map.put("date", "2019年10月9日13:28:28"); excelWriter.fill(map, writeSheet); } }
多列表组合填充填充
since
2.2.0-beta1
模板
最终效果
对象
参照:最简单的填充的对象
代码
/** * 多列表组合填充填充 * * @since 2.2.0-beta1 */ @Test public void compositeFill() { // 模板注意 用{} 来表示你要用的变量 如果本来就有"{","}" 特殊字符 用"\{","\}"代替 // {} 代表普通变量 {.} 代表是list的变量 {前缀.} 前缀可以区分不同的list String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "composite.xlsx"; String fileName = TestFileUtil.getPath() + "compositeFill" + System.currentTimeMillis() + ".xlsx"; // 方案1 try (ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build()) { WriteSheet writeSheet = EasyExcel.writerSheet().build(); FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build(); // 如果有多个list 模板上必须有{前缀.} 这里的前缀就是 data1,然后多个list必须用 FillWrapper包裹 excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); excelWriter.fill(new FillWrapper("data1", data()), fillConfig, writeSheet); excelWriter.fill(new FillWrapper("data2", data()), writeSheet); excelWriter.fill(new FillWrapper("data2", data()), writeSheet); excelWriter.fill(new FillWrapper("data3", data()), writeSheet); excelWriter.fill(new FillWrapper("data3", data()), writeSheet); Map<String, Object> map = new HashMap<String, Object>(); //map.put("date", "2019年10月9日13:28:28"); map.put("date", new Date()); excelWriter.fill(map, writeSheet); } }