EasyExcel文档

news/2024/9/18 8:55:27/文章来源:https://www.cnblogs.com/qipaoxian/p/18375029

源自官方文档,仅供我个人使用,其他人请移步官方地址: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多一点 img

帮忙点个⭐Star

开源不易,如果觉得EasyExcel对您的工作还是有帮助的话,请帮忙在github star 的右上角点个⭐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示例

img

最简单的读的对象

@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示例

img

对象

@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示例

img

对象

@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示例

img

最简单的写的对象

@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示例

img

对象

参照:最简单的写的对象

代码

    /**     * 根据参数只导出指定列     * <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示例

img

对象

@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示例

img

对象

@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示例

img

对象

参照:最简单的写的对象

代码

    /**     * 重复多次写入     * <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示例

img

对象

@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示例

img

对象

@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示例

img

对象

@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示例

img

excel示例

img

对象

参照:最简单的写的对象

代码

    /**     * 根据模板写入     * <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示例

img

对象

@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示例

img

对象

@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示例

img

对象

参照:最简单的写的对象

代码

    /**     * 拦截器形式自定义样式     * <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示例

img

对象

方法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示例

img

对象

参照:最简单的写的对象

代码

    /**     * 使用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示例

img

对象

参照:最简单的写的对象

代码

    /**     * 动态头,实时生成头写入     * <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示例

img

对象

@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示例

img

对象

参照:最简单的写的对象

定义拦截器

/** * 自定义拦截器。对第一行第一列的头超链接到: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示例

img

对象

参照:最简单的写的对象

定义拦截器

/** * 自定义拦截器.新增注释,第一行头加批注 * * @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示例

img

对象

@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示例

img

代码

    /**     * 不创建对象的写     */    @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

最简单的填充的模板

img

最终效果

img

最简单的填充的对象

@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

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 填充列表     *     * @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

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 复杂的填充     *     * @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

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 数据量大的复杂填充     * <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

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 横向的填充     *     * @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

模板

img

最终效果

img

对象

参照:最简单的填充的对象

代码

    /**     * 多列表组合填充填充     *     * @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);        }    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/785674.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在VS Code中使用Snippet Craft扩展提高编码效率

Snippet Craft 一个VS Code代码片段管理插件 功能 创建和插入代码片段 在编辑器区域右键菜单中点击插入Snippet,或在代码片段视图中点击条目,则会将代码片段插入到当前激活文档的光标位置。代码片段编辑 代码片段在左侧栏中,根据创建时的文件内容类型,分组显示代码片段,可…

cmake Makefile面试问题

摘自:https://wenku.baidu.com/view/4db705bdba0d6c85ec3a87c24028915f814d8462.html?_wkts_=1724332395577&bdQuery=cmake+%E9%9D%A2%E8%AF%95%E9%A2%98

Modern CMake 简介

摘自:https://zhuanlan.zhihu.com/p/76975231 Modern CMake 简介历史背景CMake是一个构建系统生成器(build-system generator)。常见的构建系统,有Visual Studio,XCode,Make等等。CMake可以支持不同平台下构建系统的生成。 CMake的出现已经有接近20年的历史,它的发展过程…

048、Vue3+TypeScript基础,基本的子页面和父页面相互通讯

01、main.js代码如下:// 引入createApp用于创建Vue实例 import {createApp} from vue // 引入App.vue根组件 import App from ./App.vueconst app = createApp(App);// App.vue的根元素id为app app.mount(#app)02、App.vue代码如下:<template><div class="app&…

1、拟合、预测、估算器、管道与模型评估

一、拟合和预测:估算器基础 1.1 资源导入、样本定义和训练from sklearn.ensemble import RandomForestClassifier #随机森林分类器 """ random_state=0 将使用固定的随机数种子(在这个例子中是0)来初始化随机数生成器。 这样,无论你的代码运行多少次,只要…

DDD精粹速读(一)

1 你需要知道的 - 战略设计 DDD是一种软件设计和构建方法,其重点在于独立于数据持久化等技术问题,准确表达业务规则。 不幸,DDD 对新手来说极具挑战性,部分原因是它有许多独特的概念需要学习。本文我简要介绍这些重要的思想,以便你能自信继续你的 DDD 旅程。 第一部分将侧…

消息队列作用(解耦、异步、削峰)

原文:消息队列作用(解耦、异步、削峰)图详解一、消息队列简介 简单来说,“消息队列”是在消息的传输过程中保存消息的容器。MQ 全称为 Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信。 消…

C++ 链表

1. 前言 链表:不仅存储 当前元素的数据,还存储着 元素排列顺序 2. 正题 2.1 如何存储节点? 我们可以使用 结构体 数组来存储 链表节点 struct Node {int val; // 可以是 string 或其它复杂的类型int nxt; } node[N];Tip: 下标顺序不是单链表顺序val 代表 元素本身,nxt 代表…

WPF 模拟UWP原生窗口样式——亚克力|云母材质、自定义标题栏样式、原生DWM动画 (附我封装好的类)

先看一下最终效果,左图为使用亚克力材质并添加组合颜色的效果;右图为MicaAlt材质的效果。两者都自定义了标题栏并且最大限度地保留了DWM提供的原生窗口效果(最大化最小化、关闭出现的动画、窗口阴影、拖拽布局器等)。接下来把各部分的实现一个个拆开来讲讲。 一、使用窗口材…

Redis学习(一)

1.通用命令keys * del k1 exists k1 expipe k1 ttl k12.String类型String类型的常见命令 set k1 v1 添加键值对 get k1 v1 获得键值对对应的值 mset k1 v1 k2 v2 一次性设置多个值 mget k1 k2 k3 一次性获取多个键值对的值 incr k1 让k1自增 incrby k1 2 按步长2自增 dec…

C# WebSocket Fleck 源码解读

最近在维护公司旧项目,偶然发现使用Fleck实现的WebSocket主动推送功能,(由于前端页面关闭时WebSocket Server中执行了多次OnClone事件回调并且打印了大量的关闭日志,),后来我特地看了源码,这里做一些分享 github: https://github.com/statianzo/Fleck在源码中,作者在 Sam…