目录

0.环境说明

1. EasyExcel简介

2. 项目配置

3. EasyExcel读取Excel文件并对文件表头进行校验

4. EasyExcel写入Excel文件并根据文件内容自适应宽高


0.环境说明

  • java 1.8
  • IDEA 2022.3.1
  • EasyExcel 3.1.0

1. EasyExcel简介

        EasyExcel是阿里巴巴开源的 Excel 读取框架,其最大的特点是极低的内存消耗和极高的文件读取速度。在本文中使用 EasyExcel,实现了读取 Excel 文件并对文件表头进行校验写入 Excel文件并根据写入内容自适应调整单元格宽高

        其他更详细的信息,可以查阅官网:EasyExcel官方文档

2. 项目配置

        在开始编码之前,需要先引入 EasyExcel、lombok 和 fastjson 依赖。

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.60</version>
</dependency>

3. EasyExcel 读取 Excel 文件并对文件表头进行校验

        需要读取的 Excel 文件如下:

excel java 文件校验 java excel导入 校验_EasyExcel

        定义与 Excel 文件相对应的实体类。lombok 的 @Data 注解会为实体类自动生成 get 、set 以及 toString 方法。EasyExcel 的 @ExcelProperty 注解,定义了与 javaBean 字段相对应的 Excel 字段。

@Data
public class ExcelDTO {
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("年龄")
    private int age;
    @ExcelProperty("分数")
    private Double score;
}

         代码实现读取 Excel 文件,读取的数据被封装进 List<ExcelDTO> 中。

@Test
public void readExcel() {
    File file = new File("src/main/resources/ExcelDemo.xlsx");
    //默认读取Excel文件的第一个sheet
    List<ExcelDTO> list = EasyExcel.read(file).head(ExcelDTO.class).sheet().doReadSync();
    for (ExcelDTO data : list) {
        System.out.println(data);
    }
}

         如果想实现在读取Excel文件的同时,对表头和文件内容进行校验,则需要定义一个 Listener 实现 EasyExcel 提供的 ReadListener 接口,实现代码如下。

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.Map;

public class DemoDataListener extends AnalysisEventListener<ExcelDTO> {
    /**
     * 每解析一条数据,都会来调用该方法
     * 对所有数据进行校验,在此增加校验逻辑
     *
     * @param excelDTO
     * @param analysisContext
     */
    @Override
    public void invoke(ExcelDTO excelDTO, AnalysisContext analysisContext) {
    }

    /**
     * 每解析一行表头,会调用该方法
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        if (!headMap.containsKey(0) || !headMap.containsKey(1) || !headMap.containsKey(2)
                || !headMap.get(0).equals("姓名") || !headMap.get(1).equals("年龄")
                || !headMap.get(2).equals("分数")) {
            throw new RuntimeException("表头校验失败");
        }
    }

    /**
     * 所有数据都解析完成后,会调用该方法
     *
     * @param analysisContext
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    }
}

        同时,在读取Excel文件时,也需要加入 Listener ,实现的代码如下:

@Test
public void readExcel() {
    File file = new File("src/main/resources/ExcelDemo.xlsx");
    DemoDataListener listener = new DemoDataListener();
    List<ExcelDTO> list = new ArrayList<>();
    try {
        //默认读取Excel文件的第一个sheet
        list = EasyExcel.read(file, listener).head(ExcelDTO.class).sheet().doReadSync();
    } catch (Exception e) {
        //此处可修改为warn级别日志打印
        System.out.println("表头校验失败");
    }

    for (ExcelDTO data : list) {
        System.out.println(data);
    }
}

4. EasyExcel 写入 Excel 文件并根据文件内容自适应宽高

        首先初始化一个 List<ExcelDTO> ,将 name 字段的内容,设置的长一些,通过如下代码可以将数据写入到 Excel 表格。

@Test
public void writeExcel() {
    List<ExcelDTO> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        ExcelDTO excelDTO = new ExcelDTO();
        excelDTO.setName("名字名字名字名字名字名字名字" + i);
        excelDTO.setAge(i + 30);
        excelDTO.setScore((double) (60 + i));
        list.add(excelDTO);
    }
    File file = new File("src/main/resources/WriteDemo.xlsx");
    EasyExcel.write("demo1.xlsx", ExcelDTO.class).sheet("模板").doWrite(list);
}

        写入 Excel 文件的内容如下所示,可以看到姓名字段因为内容过长,而被隐藏了一部分,查看 Excel 文件时不便捷。

excel java 文件校验 java excel导入 校验_excel_02

         这时,自定义一个 Config 类继承自 EasyExcel 的 AbstractColumnWidthStyleStrategy 类,达到根据内容自适应宽度的效果。

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CustomCellWriteWeightConfig extends AbstractColumnWidthStyleStrategy {
    private final Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            //computeIfAbsent方法的作用为,在通过key获取value时,若key对应的value存在,则返回value值;若不存在,则创建一个符合条件的value并返回
            Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>());

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 254) {
                    columnWidth = 254;
                }
                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                //需要将列宽,设置为当前列最长内容的长度
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                    Sheet sheet = writeSheetHolder.getSheet();
                    sheet.setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }
            }
        }
    }

    /**
     * 计算当前内容的长度,(实际就是获取当前内容的字节长度)
     *
     * @param cellDataList
     * @param cell
     * @param isHead
     * @return
     */
    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        // 若存在换行符,则需要根据换行符的位置进行针对性处理
                        int index = cellData.getStringValue().indexOf("\n");
                        return index != -1 ?
                                cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

        同样的,自定义一个 Config 类继承自 EasyExcel 的 AbstractRowHeightStyleStrategy 类,达到自适应高度的效果。

import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import java.util.Iterator;
import java.util.Objects;

public class CustomCellWriteHeightConfig extends AbstractRowHeightStyleStrategy {
    /**
     * 默认高度
     */
    private static final Integer DEFAULT_HEIGHT = 300;

    /**
     * 设置表头的高度
     *
     * @param row
     * @param relativeRowIndex
     */
    @Override
    protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
    }

    /**
     * 设置内容的高度
     *
     * @param row
     * @param relativeRowIndex
     */
    @Override
    protected void setContentColumnHeight(Row row, int relativeRowIndex) {
        Iterator<Cell> cellIterator = row.cellIterator();
        if (!cellIterator.hasNext()) {
            return;
        }
        // 默认为 1行高度
        int maxHeight = 1;
        while (cellIterator.hasNext()) {
            Cell cell = cellIterator.next();
            if (Objects.requireNonNull(cell.getCellTypeEnum()) == CellType.STRING) {
                if (cell.getStringCellValue().contains("\n")) {
                    int length = cell.getStringCellValue().split("\n").length;
                    maxHeight = Math.max(maxHeight, length);
                }
            }
        }
        row.setHeight((short) (maxHeight * DEFAULT_HEIGHT));
    }
}

        在写入 Excel 文件时,将自定义的两个 Config 类注册到 EasyExcel 中。

@Test
public void writeExcel() {
    List<ExcelDTO> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        ExcelDTO excelDTO = new ExcelDTO();
        excelDTO.setName("名字名字名字名字名字名字名字" + i);
        excelDTO.setAge(i + 30);
        excelDTO.setScore((double) (60 + i));
        list.add(excelDTO);
    }
    File file = new File("src/main/resources/WriteDemo.xlsx");
    EasyExcel.write("demo1.xlsx", ExcelDTO.class).sheet("模板").
            registerWriteHandler(new CustomCellWriteWeightConfig()).registerWriteHandler(new CustomCellWriteHeightConfig()).doWrite(list);
}

        写入的 Excel 文件如图,可以看到宽度已经经过自适应调整了。

excel java 文件校验 java excel导入 校验_excel java 文件校验_03