Java操作Excel三种方式POI、Hutool、EasyExcel

1. Java操作Excel概述

1.1 Excel需求概述

Excel作为常用的报表文件,项目开发中经常用到Excel操作功能,对于Excel操作目前常见的有三种方式,Apache的POI,Hutool的ExcelUtil,Ali的EasyExcel。

1.2 Excel操作三种方式对比

Apache的POI相对比较原生,功能强大,使用复杂繁琐,内存占用比较大。
Hutool的ExcelUtil在POI基础上进行封装,简单易用,功能不太全,对于一些特殊操作,如标注等不具备。
Ali的EasyExcel也是在POI基础上进行封装,简单易用,功能相对比较全面,且性能十分强悍,最为推荐。

2. ApachePOIExcel

2.1 ApachePOI简介

Apache POI [1] 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。

2.2 ApachePOI功能结构

HSSF [1]  - 提供读写Microsoft Excel XLS格式档案的功能。
XSSF [1]  - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。
HWPF [1]  - 提供读写Microsoft Word DOC格式档案的功能。
HSLF [1]  - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF [1]  - 提供读Microsoft Visio格式档案的功能。
HPBF [1]  - 提供读Microsoft Publisher格式档案的功能。
HSMF [1]  - 提供读Microsoft Outlook格式档案的功能。

2.3 ApachePOI官网说明

POI官网地址:https://poi.apache.org/index.html

POI下载地址:https://poi.apache.org/download.html

POI文档说明:https://poi.apache.org/apidocs/index.html

在这里插入图片描述

2.4 ApachePOI实现验证

Maven依赖配置

        <!-- poi 相关 -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.1</version>
        </dependency>

代码测试

package com.zrj.easyexcel.excel;

import com.google.common.collect.Lists;
import org.junit.Test;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 原生Excel测试类
 *
 * @author zrj
 * @since 2021/12/29
 **/
public class EasyExcelPoi {

    @Test
    public void createExcelTest() throws Exception {
        //文件的基础路径
        String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;
        System.out.println("文件的基础路径:" + fileBasePath);

        String filepath = fileBasePath + "writeexcel.xlsx";
        String sheetName = "模板名称";
        List<String> titles = Lists.newArrayList("name", "sex", "age");
        List<Map<String, Object>> values = Lists.newArrayList();

        Map map2 = new HashMap(6);
        map2.put("name", "王五");
        map2.put("sex", "女");
        map2.put("age", "20");
        values.add(map2);

        Map map3 = new HashMap(6);
        map3.put("name", "阿萨");
        map3.put("sex", "女");
        map3.put("age", "66");
        values.add(map3);

        ExcelUtils.writeExcel(filepath, sheetName, titles, values);
        System.out.println("创建Excel完成");
    }

    @Test
    public void copyExcelTest() throws Exception {
        //文件的基础路径
        String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;
        System.out.println("文件的基础路径:" + fileBasePath);

        String filepath = fileBasePath + "writeexcel.xlsx";
        String fileNewPath = fileBasePath + "writeexcel2.xlsx";
        ExcelUtils.writeExcel(filepath, fileNewPath);
        System.out.println("复制Excel完成");
    }

    @Test
    public void readExcelTest() throws Exception {
        //文件的基础路径
        String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;
        String filepath = fileBasePath + "writeexcel.xlsx";
        System.out.println("文件路径:" + fileBasePath);

        String readExcel = ExcelUtils.readExcel(filepath);
        System.out.println(readExcel);
        System.out.println("读取Excel完成");
    }


}

3. HutoolExcel

3.1 Hutool简介

Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。

Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当;

Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。

项目中使用非常广泛的一个小工具,没有依赖,非常干净好用。
官网地址:https://www.hutool.cn/

参考文档:https://www.hutool.cn/docs/#/
API文档:https://apidoc.gitee.com/dromara/hutool/

GITEE:https://gitee.com/dromara/hutool/
GITHUB:https://github.com/dromara/hutool/

视频介绍:https://player.bilibili.com/player.html?aid=710062843&bvid=BV1bQ4y1M7d9&cid=170377135&page=2

3.2 Hutool组件

在这里插入图片描述

3.1 HutoolExcel实现验证

Mave依赖配置

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.18</version>
</dependency>

代码实现验证

package com.zrj.easyexcel.excel;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.zrj.easyexcel.utils.FileUtils;
import org.junit.Test;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * HutoolExcel测试类
 * 注意:不要到错包了,easyexcel与hutool的类名一样!!!
 *
 * @author zrj
 * @since 2021/12/29
 **/
public class EasyExcelHutool {

    /*******************************Excel读取-ExcelReader*******************************************/

    /**
     * 1. 读取Excel中所有行和列,都用列表表示
     */
    @Test
    public void readExcelObjectTest() {
        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
        //通过工具类创建ExcelReader
        ExcelReader reader = ExcelUtil.getReader(filepath);
        List<List<Object>> readAll = reader.read();
        System.out.println("Excel读取结果:" + readAll);
    }

    /**
     * 2. 读取为Map列表,默认第一行为标题行,Map中的key为标题,value为标题对应的单元格值。
     */
    @Test
    public void readExcelMapTest() {
        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
        //通过工具类创建ExcelReader
        ExcelReader reader = ExcelUtil.getReader(filepath);
        List<Map<String, Object>> readAll = reader.readAll();
        System.out.println("Excel读取结果:" + readAll);
    }

    /**
     * 3. 读取为Bean列表,Bean中的字段名为标题,字段值为标题对应的单元格值。
     */
    @Test
    public void readExcelBeanTest() {
        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
        //通过工具类创建ExcelReader
        ExcelReader reader = ExcelUtil.getReader(filepath);
        List<UserBean> readAll = reader.readAll(UserBean.class);
        System.out.println("Excel读取结果:" + readAll);
    }


    /*******************************Excel生成-ExcelWriter*******************************************/

    /**
     * 1. 将行列对象写出到Excel
     */
    @Test
    public void writeExcelObjectTest() {
        //定义一个嵌套的List,List的元素也是一个List,内层的一个List代表一行数据,每行都有4个单元格,最终list对象代表多行数据。
        List<String> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd");
        List<String> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1");
        List<String> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2");
        List<String> row4 = CollUtil.newArrayList("aa3", "bb3", "cc3", "dd3");
        List<String> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4");

        List<List<String>> rows = CollUtil.newArrayList(row1, row2, row3, row4, row5);

        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeTest.xlsx";
        //通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter(filepath);
        //通过构造方法创建writer
        //ExcelWriter writer = new ExcelWriter("d:/writeTest.xls");

        //跳过当前行,既第一行,非必须,在此演示用
        writer.passCurrentRow();

        //合并单元格后的标题行,使用默认标题样式
        writer.merge(row1.size() - 1, "测试标题");
        //一次性写出内容,强制输出标题
        writer.write(rows, true);
        //关闭writer,释放内存
        writer.close();
        System.out.println("创建完成,文件路径:" + filepath);
    }

    /**
     * 2. 写出Map数据
     */
    @Test
    public void writeExcelMapTest() {
        Map<String, Object> row1 = new LinkedHashMap<>();
        row1.put("姓名", "张三");
        row1.put("年龄", 23);
        row1.put("成绩", 88.32);
        row1.put("是否合格", true);
        row1.put("考试日期", DateUtil.date());

        Map<String, Object> row2 = new LinkedHashMap<>();
        row2.put("姓名", "李四");
        row2.put("年龄", 33);
        row2.put("成绩", 59.50);
        row2.put("是否合格", false);
        row2.put("考试日期", DateUtil.date());

        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList(row1, row2);

        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeMapTest.xlsx";
        // 通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter(filepath);
        // 合并单元格后的标题行,使用默认标题样式
        writer.merge(row1.size() - 1, "一班成绩单");
        // 一次性写出内容,使用默认样式,强制输出标题
        writer.write(rows, true);
        // 关闭writer,释放内存
        writer.close();
        System.out.println("创建完成,文件路径:" + filepath);
    }

    /**
     * 3. 写出Bean数据
     */
    @Test
    public void writeExcelBeanTest() {
        UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();
        UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();

        List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);

        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanTest.xlsx";
        // 通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter(filepath);
        // 合并单元格后的标题行,使用默认标题样式
        writer.merge(4, "一班成绩单");
        // 一次性写出内容,使用默认样式,强制输出标题
        writer.write(rows, true);
        // 关闭writer,释放内存
        writer.close();
        System.out.println("创建完成,文件路径:" + filepath);
    }

    /**
     * 4. 自定义Bean的key别名(排序标题)
     * 注意:与3的区别是,3中标题是英文,4中标题是自定义的别名
     */
    @Test
    public void writeExcelBeanKeyTest() {
        UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();
        UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();

        List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);

        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";
        // 通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter(filepath);

        //自定义标题别名
        writer.addHeaderAlias("name", "姓名");
        writer.addHeaderAlias("age", "年龄");
        writer.addHeaderAlias("score", "分数");
        writer.addHeaderAlias("isPass", "是否通过");
        writer.addHeaderAlias("examDate", "考试时间");

        // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);

        // 合并单元格后的标题行,使用默认标题样式
        writer.merge(4, "一班成绩单");
        // 一次性写出内容,使用默认样式,强制输出标题
        writer.write(rows, true);
        // 关闭writer,释放内存
        writer.close();
        System.out.println("创建完成,文件路径:" + filepath);
    }

    /**
     * 5. 写出到流
     */
    @Test
    public void writeExcelStreamTest() {
        //定义内容
        UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();
        UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();
        List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);

        //定义输出流
        ByteArrayOutputStream out = FileUtils.cloneInputStream(IoUtil.toUtf8Stream(rows.toString()));

        //定义文件路径
        String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeStreamTest.xlsx";
        if (!FileUtil.isFile(filepath)) {
            FileUtil.touch(filepath);
        }
        //通过工具类创建writer
        ExcelWriter writer = ExcelUtil.getWriter();
        //创建xlsx格式的
        //ExcelWriter writer = ExcelUtil.getWriter(true);
        //一次性写出内容,使用默认样式,强制输出标题
        writer.write(rows, true);
        //out为OutputStream,需要写出到的目标流
        writer.flush(out);
        //关闭writer,释放内存
        writer.close();
        System.out.println("创建完成,文件路径:" + filepath);
    }

    /**
     * 6. 写出到客户端下载(写出到Servlet)
     */
    @Test
    public void writeExcelDownloadTest() {
        //定义内容
        UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();
        UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();
        List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);

        // 通过工具类创建writer,默认创建xls格式
        ExcelWriter writer = ExcelUtil.getWriter();
        // 一次性写出内容,使用默认样式,强制输出标题
        writer.write(rows, true);
        //out为OutputStream,需要写出到的目标流

        //response为HttpServletResponse对象
        //response.setContentType("application/vnd.ms-excel;charset=utf-8");
        //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码
        //response.setHeader("Content-Disposition", "attachment;filename=test.xls");
        //ServletOutputStream out = response.getOutputStream();

        //writer.flush(out, true);
        //关闭writer,释放内存
        writer.close();
        //此处记得关闭输出Servlet流
        //IoUtil.close(out);
    }

}

4. EasyExcel

4.1 EasyExcel简介

EasyExcel是阿里巴巴开源的一款Excel操作工具,简单易用,功能强大,节省内存,性能彪悍。

语雀地址:https://www.yuque.com/easyexcel/doc/easyexcel

Github地址: https://github.com/alibaba/easyexcel

mvn依赖地址:https://mvnrepository.com/artifact/com.alibaba/easyexcel

4.2 HutoolExcel实现验证

Mave依赖配置

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.0.5</version>
        </dependency>

EasyExcel基本实现案例
Github案例地址:https://github.com/alibaba/easyexcel
项目路径:test -> read/write -> ReadTest与WriteTest类,实现基本Excel各种读写功能。

EasyExcel批量添加批注
ByteToInputStreamUtil

package com.zrj.easyexcel.excel;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;

/**
 * 输入流与字节转换
 *
 * @author zrj
 * @since 2021/12/27
 **/
public class ByteToInputStreamUtil {
    /**
     * 字节转输入流
     *
     * @param buf
     * @return java.io.InputStream
     */
    public static InputStream byte2Input(byte[] buf) {
        return new ByteArrayInputStream(buf);
    }

    /**
     * 输入流转字节
     *
     * @param inStream
     * @return byte[]
     */
    public static byte[] input2byte(InputStream inStream) throws Exception {
        ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while ((rc = inStream.read(buff, 0, 100)) > 0) {
            swapStream.write(buff, 0, rc);
        }
        byte[] in2b = swapStream.toByteArray();
        return in2b;
    }
}

CommentWriteHandler

package com.zrj.easyexcel.excel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.write.handler.AbstractRowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 批注处理器
 *
 * @author zrj
 * @since 2021/12/29
 **/
public class CommentWriteHandler extends AbstractRowWriteHandler {

    /**
     * sheet名称KEY
     */
    public static final String SHEETNAME_NAME = "sheetName";

    /**
     * 文档后缀名
     */
    private String extension;

    /**
     * 列索引key
     */
    public static final String COLINDEX_NAME = "colIndex";

    /**
     * 行索引key
     */
    public static final String ROWINDEX_NAME = "rowIndex";

    /**
     * 批注内容key
     */
    public static final String COMMENTCONTENT_NAME = "commentContent";

    /**
     * sheet页名称列表
     */
    private List<String> sheetNameList;

    /**
     * 批注集合
     */
    List<Map<String, String>> commentList = new ArrayList<>(10);

    /**
     * CommentWriteHandler
     *
     * @param commentList
     * @param extension
     */
    public CommentWriteHandler(List<Map<String, String>> commentList, String extension) {
        this.commentList = commentList != null && commentList.size() > 0
                ? commentList.stream().filter(x ->
                x.keySet().contains(SHEETNAME_NAME) == true && x.get(SHEETNAME_NAME) != null && StrUtil.isNotBlank(x.get(SHEETNAME_NAME).toString())
                        && x.keySet().contains(COLINDEX_NAME) == true && x.get(COLINDEX_NAME) != null && StrUtil.isNotBlank(x.get(COLINDEX_NAME).toString())
                        && x.keySet().contains(ROWINDEX_NAME) == true && x.get(ROWINDEX_NAME) != null && StrUtil.isNotBlank(x.get(ROWINDEX_NAME).toString())
                        && x.keySet().contains(COMMENTCONTENT_NAME) == true && x.get(COMMENTCONTENT_NAME) != null && StrUtil.isNotBlank(x.get(COMMENTCONTENT_NAME).toString())
        ).collect(Collectors.toList()) : new ArrayList<>();
        sheetNameList = this.commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());
        this.extension = extension;
    }

    /**
     * 生成批注信息
     *
     * @param sheetName      sheet页名称
     * @param rowIndex       行号
     * @param columnIndex    列号
     * @param commentContent 批注内容
     * @return
     */
    public static Map<String, String> createCommentMap(String sheetName, int rowIndex, int columnIndex, String commentContent) {
        Map<String, String> map = new HashMap<>();
        //sheet页名称
        map.put(SHEETNAME_NAME, sheetName);
        //行号
        map.put(ROWINDEX_NAME, rowIndex + "");
        //列号
        map.put(COLINDEX_NAME, columnIndex + "");
        //批注内容
        map.put(COMMENTCONTENT_NAME, commentContent);
        return map;
    }

    /**
     * 功能描述
     * @param writeSheetHolder
     * @param writeTableHolder
     * @param row
     * @param relativeRowIndex
     * @param isHead
     * @return void
     */
    @Override
    public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                Integer relativeRowIndex, Boolean isHead) {
        Sheet sheet = writeSheetHolder.getSheet();
        //不需要添加批注,或者当前sheet页不需要添加批注
        if (commentList == null || commentList.size() <= 0 || sheetNameList.contains(sheet.getSheetName()) == false) {
            return;
        }
        //获取当前行的批注信息
        List<Map<String, String>> rowCommentList = commentList.stream().filter(x ->
                StrUtil.equals(x.get(SHEETNAME_NAME).toString(), sheet.getSheetName())
                        && relativeRowIndex == Integer.parseInt(x.get(ROWINDEX_NAME))).collect(Collectors.toList());
        //当前行没有批注信息
        if (rowCommentList == null || rowCommentList.size() <= 0) {
            return;
        }
        List<String> colIndexList = rowCommentList.stream().map(x -> x.get(COLINDEX_NAME)).distinct().collect(Collectors.toList());
        for (String colIndex : colIndexList) {
            //同一单元格的批注信息
            List<Map<String, String>> cellCommentList = rowCommentList.stream().filter(x ->
                    StrUtil.equals(colIndex, x.get(COLINDEX_NAME))).collect(Collectors.toList());
            if (CollectionUtil.isEmpty(cellCommentList)) {
                continue;
            }
            //批注内容拼成一条
            String commentContent = cellCommentList.stream().map(x -> x.get(COMMENTCONTENT_NAME)).collect(Collectors.joining());
            Cell cell = row.getCell(Integer.parseInt(colIndex));
            addComment(cell, commentContent, extension);
        }
        //删除批注信息
        commentList.remove(rowCommentList);
        //重新获取要添加的sheet页姓名
        sheetNameList = commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());
    }

    /**
     * 给Cell添加批注
     *
     * @param cell      单元格
     * @param value     批注内容
     * @param extension 扩展名
     */
    public static void addComment(Cell cell, String value, String extension) {
        Sheet sheet = cell.getSheet();
        cell.removeCellComment();
        if ("xls".equals(extension)) {
            ClientAnchor anchor = new HSSFClientAnchor();
            // 关键修改
            anchor.setDx1(0);
            anchor.setDx2(0);
            anchor.setDy1(0);
            anchor.setDy2(0);
            anchor.setCol1(cell.getColumnIndex());
            anchor.setRow1(cell.getRowIndex());
            anchor.setCol2(cell.getColumnIndex() + 5);
            anchor.setRow2(cell.getRowIndex() + 6);
            // 结束
            Drawing drawing = sheet.createDrawingPatriarch();
            Comment comment = drawing.createCellComment(anchor);
            // 输入批注信息
            comment.setString(new HSSFRichTextString(value));
            // 将批注添加到单元格对象中
            cell.setCellComment(comment);
        } else if ("xlsx".equals(extension)) {
            ClientAnchor anchor = new XSSFClientAnchor();
            // 关键修改
            anchor.setDx1(0);
            anchor.setDx2(0);
            anchor.setDy1(0);
            anchor.setDy2(0);
            anchor.setCol1(cell.getColumnIndex());
            anchor.setRow1(cell.getRowIndex());
            anchor.setCol2(cell.getColumnIndex() + 5);
            anchor.setRow2(cell.getRowIndex() + 6);
            // 结束
            Drawing drawing = sheet.createDrawingPatriarch();
            Comment comment = drawing.createCellComment(anchor);
            // 输入批注信息
            comment.setString(new XSSFRichTextString(value));
            // 将批注添加到单元格对象中
            cell.setCellComment(comment);
        }
    }
}

EasyExcelUtils

package com.zrj.easyexcel.excel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.zrj.easyexcel.entity.DemoData;
import com.zrj.easyexcel.excel.read.ReadTest;
import com.zrj.easyexcel.excel.write.WriteTest;
import org.junit.Test;

import java.io.File;
import java.util.*;

/**
 * EasyExcel测试类
 *
 * @author zrj
 * @since 2021/12/29
 **/
public class EasyExcelUtils {
    /**
     * EasyExcel读写测试类
     */
    @Test
    public void easyExcelReadWriteTest() {
        //EasyExcel读测试类
        ReadTest readTest = new ReadTest();
        //EasyExcel写测试类
        WriteTest writeTest = new WriteTest();
    }


    /**
     * EasyExcel 批量添加批注
     */
    @Test
    public void batchAddCommentTest() {
        try {
            //构建数据
            List<DemoData> demoDataList = new ArrayList<>(10);
            demoDataList.add(DemoData.builder().string("张三").date(new Date()).doubleData(3.14).build());
            demoDataList.add(DemoData.builder().string("王五").date(new Date()).doubleData(6.68).build());
            demoDataList.add(DemoData.builder().string("赵六").date(new Date()).doubleData(8.32).build());
            demoDataList.add(DemoData.builder().string("李四").date(new Date()).doubleData(8.66).build());

            //定义文件路径
            String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "EasyExcelCommentWriteTest3.xlsx";
            String sheetName = "批注模板";
            List<Map<String, String>> commentList = new ArrayList<>();
            commentList.add(CommentWriteHandler.createCommentMap(sheetName, 0, 1, "第一条批注。"));
            commentList.add(CommentWriteHandler.createCommentMap(sheetName, 1, 1, "第二条批注。"));

            // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
            // 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。
            EasyExcel.write(filepath, DemoData.class).inMemory(Boolean.TRUE)
                    .registerWriteHandler(new CommentWriteHandler(commentList, "xlsx"))
                    .sheet(sheetName).doWrite(demoDataList);

            System.out.println("批注模板完成,模板地址:" + filepath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

5. 源码地址

测试源码地址:https://gitee.com/rjzhu/opencode/tree/master/easyexcel

  • 6
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值