目录
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 文件相对应的实体类。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 文件时不便捷。
这时,自定义一个 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 文件如图,可以看到宽度已经经过自适应调整了。