EasyExcel读写文件
1、写文件
1.1 定义实体类
@Data
public class DemoExportDto {
@ExcelProperty("生成时间")
private String createTime;
@ExcelProperty("订单号")
private String orderNo;
@ExcelProperty("状态")
private String status;
@ExcelProperty("更新时间")
private String updateTime;
@ExcelProperty("地址")
private String address;
}
1.2 获取表头样式
/**
* 获取写表头的样式
*
* @return 返回值
*/
public static WriteCellStyle getHeadWriteCellStyle() {
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
WriteFont writeFont = new WriteFont();
writeFont.setColor(IndexedColors.BLACK.getIndex());
writeFont.setBold(Boolean.TRUE);
writeFont.setFontHeightInPoints((short) 15);
headWriteCellStyle.setWriteFont(writeFont);
return headWriteCellStyle;
}
1.3 获取内容样式
/**
* 获取写内容的样式
*
* @return 返回值
*/
public static WriteCellStyle getContentWriteCellStyle() {
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
WriteFont writeFont = new WriteFont();
writeFont.setColor(IndexedColors.BLACK.getIndex());
writeFont.setBold(Boolean.FALSE);
writeFont.setFontHeightInPoints((short) 12);
contentWriteCellStyle.setWriteFont(writeFont);
return contentWriteCellStyle;
}
1.4 定义标注处理器
public class CellCommentWriteHandler implements CellWriteHandler {
@Override
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
List<WriteCellData<?>> cellDataList, Cell cell, Head head,
Integer relativeRowIndex, Boolean isHead) {
addCellComment(cell);
}
/**
* 给表头添加批注信息
*
* @param cell 单元格
*/
private void addCellComment(Cell cell) {
int columnIndex = cell.getColumnIndex();
int rowIndex = cell.getRowIndex();
if (rowIndex < 1) {
Drawing<?> drawing = cell.getSheet().createDrawingPatriarch();
Comment cellComment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, columnIndex, rowIndex, columnIndex + 2, rowIndex + 6));
String value = cell.getStringCellValue();
cellComment.setString(new XSSFRichTextString(value + "这是标注信息..."));
cell.setCellComment(cellComment);
}
}
}
1.5 写文件
1.5.1 全量写入文件
public static void writeFileWithCustomStyle(String file, List<DemoExportDto> list) {
WriteCellStyle headWriteCellStyle = ExportHandler.getHeadWriteCellStyle();
WriteCellStyle contentWriteCellStyle = ExportHandler.getContentWriteCellStyle();
EasyExcelFactory.write(file, DemoExportDto.class)
// 指定表头
.head(DemoExportDto.class)
// 写入sheet的名称
.sheet("默认sheet1名称")
// 注册自定义写入Handler,分别为:自适应列宽、自定义样式、自定义标注处理器
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle))
.registerWriteHandler(new CellCommentWriteHandler())
// 开始写入数据
.doWrite(list);
}
1.5.2 分批写入文件
public static void writeFileWithCustomStyle2(String file) {
ExcelWriter excelWriter = null;
try {
WriteCellStyle headWriteCellStyle = ExportHandler.getHeadWriteCellStyle();
WriteCellStyle contentWriteCellStyle = ExportHandler.getContentWriteCellStyle();
excelWriter = EasyExcelFactory.write(file, DemoExportDto.class)
// 注册自定义写入Handler,分别为:自适应列宽、自定义样式、自定义标注处理器
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle))
.registerWriteHandler(new CellCommentWriteHandler())
.build();
// 这里注意:如果同一个sheet只要创建一次,持有excelWriter以及writeSheet对象引用即可分批次写入文件,最后在关闭流
WriteSheet writeSheet = EasyExcel.writerSheet("sheet表格名称").build();
for (int i = 0; i < 10; i++) {
List<LrwMainExportDto> list = ExportHandler.formatDataList();
excelWriter.write(list, writeSheet);
}
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
LongestMatchColumnWidthStyleStrategy:自适应列宽处理器,内置处理器
HorizontalCellStyleStrategy:单元格样式处理器,内置处理器
CellCommentWriteHandler:单元格标注处理器,自定义处理器
2、读文件
2.1 自定义分页处理器
public class PageReadListener<T> implements ReadListener<T> {
/**
* Single handle the amount of data
*/
public final int batchCount;
/**
* Temporary storage of data
*/
private List<T> cachedData;
/**
* consumer
*/
private final Consumer<List<T>> consumer;
public PageReadListener(Consumer<List<T>> consumer) {
this.consumer = consumer;
this.batchCount = 1000;
cachedData = ListUtils.newArrayListWithExpectedSize(batchCount);
}
public PageReadListener(int batchCount, Consumer<List<T>> consumer) {
this.consumer = consumer;
this.batchCount = batchCount;
cachedData = ListUtils.newArrayListWithExpectedSize(batchCount);
}
@Override
public void invoke(T data, AnalysisContext context) {
cachedData.add(data);
if (cachedData.size() >= batchCount) {
consumer.accept(cachedData);
// 存储完成清理 list
cachedData = ListUtils.newArrayListWithExpectedSize(batchCount);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
consumer.accept(cachedData);
}
}
2.2 分页读取文件并重新写文件
private void doAnalyzeAndWrite(InputStream inputStream, ExcelWriter excelWriter, WriteSheet writeSheet) {
AtomicInteger index = new AtomicInteger(1);
AtomicReference<Integer> size = new AtomicReference<>(0);
EasyExcel
.read(inputStream, DemoExportDto.class, new PageReadListener<DemoExportDto>(1000, lines -> {
log.info("开始处理第 {} 页数据!共:{} 条!", index.getAndIncrement(), lines.size());
// 更近总记录数
size.updateAndGet(v -> v + lines.size());
// 判断任务运行状态,如果为false,则不在调用下游接口
if (Boolean.TRUE.equals(this.running)) {
// 批量处理该页的所有数据
this.doAnalysis(lines);
}
// 将处理结果追加写入文件中
excelWriter.write(lines, writeSheet);
}))
// 忽略空行
.ignoreEmptyRow(Boolean.TRUE)
// 读取第一个sheet页
.sheet(0)
// 表头行数
.headRowNumber(1)
// 自动Trim
.autoTrim(Boolean.TRUE)
.doReadSync();
log.info("处理文件完成,共得处理数据:{}条", size.get());
}
2.3 自定义读取文件监听器
自定义监听器,用来处理Excel文件,读取出来获取表头内容以及将数据表示为Map类型,key为表头,value为表格内容。
@Slf4j
public class ExcelListener extends AnalysisEventListener<Object> {
private final Integer maxRow;
private final Integer maxColumn;
@Getter
private final Map<Integer, String> tableHeadMap = new HashMap<>(32);
@Getter
private final List<Map<String, String>> content = new ArrayList<>(100);
public ExcelListener(Integer maxRow) {
this.maxRow = maxRow;
this.maxColumn = 100;
}
/**
* 新的构造器,传入解析Excel最大行数以及最大列数,如果不传,默认解析最大100列数据
*
* @param maxRow 最大行数
* @param maxColumn 最大列数
*/
public ExcelListener(Integer maxRow, Integer maxColumn) {
this.maxRow = maxRow;
this.maxColumn = maxColumn;
}
/**
* 读取完一行数据后执行的方法
*
* @param data 一行数据
* @param context 上下文
*/
@Override
@SuppressWarnings("unchecked")
public void invoke(Object data, AnalysisContext context) {
Integer line = null;
Integer column = null;
try {
ReadRowHolder readRowHolder = context.readRowHolder();
Integer rowIndex = readRowHolder.getRowIndex();
line = rowIndex + 1;
Map<Integer, Cell> cellMap = readRowHolder.getCellMap();
Map<String, String> map = new HashMap<>(32);
for (Map.Entry<Integer, Cell> entry : cellMap.entrySet()) {
Integer index = entry.getKey();
column = index + 1;
if (column > maxColumn) {
String message = String.format("解析第%s行时,数据列共有%s列,超过最大限制%s,后续列不在解析", line, cellMap.size(), maxColumn);
log.warn(message);
break;
}
String title = tableHeadMap.get(index);
Cell cell = entry.getValue();
if (cell instanceof ReadCellData) {
ReadCellData<String> readCellData = (ReadCellData<String>) cell;
String value = this.getCellValue(line, column, title, readCellData);
// 对value值进行trim处理
if (StringUtils.isNotBlank(value)) {
value = value.trim();
}
map.put(title, value);
} else {
log.info("其余字段类型不处理!单元格类型:{},当前行数:{}", cell.getClass(), line);
}
}
content.add(map);
} catch (Exception e) {
String message = String.format("解析第%s行 第%s列 内容出错!错误信息为:%s", line, column, e.getMessage());
log.error("解析Excel内容出错,{}", message, e);
throw new BizException(message);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("读取Excel结束!共读取结果:{}行", content.size());
}
/**
* 处理表头信息,第二行为表头中文数据,将表头数据添加到表头的Map中
* Map.Key = 表头下标
* Map.Value = 表头中文名称
*
* @param headMap 表头数据
* @param context 上下文
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
Integer line = null;
Integer column = null;
try {
ReadRowHolder readRowHolder = context.readRowHolder();
Integer row = readRowHolder.getRowIndex();
line = row + 1;
if (log.isInfoEnabled()) {
log.info("第 {} 行表头为:{}", line, JSON.toJSONString(headMap));
}
if (row == 1) {
// 将表头中的特殊字符去掉
for (Map.Entry<Integer, String> entry : headMap.entrySet()) {
Integer index = entry.getKey();
column = index + 1;
// 最大解析 maxColumn 的列,后续不解析
if (column > maxColumn) {
String message = String.format("解析第%s行时,表头列共有%s列,超过最大限制%s,后续表头不在解析", line, headMap.size(), maxColumn);
log.error(message);
break;
}
String title = entry.getValue();
tableHeadMap.put(index, this.formatString(title));
}
}
} catch (Exception e) {
String message = String.format("解析第%s行 第%s列 表头出错!错误信息为:%s", line, column, e.getMessage());
log.error("解析Excel表头出错,{}", message, e);
throw new BizException(message);
}
}
/**
* 处理批注等额外信息
*
* @param extra extra information
* @param context analysis context
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
log.info("读取到了一条额外信息:{}", JSON.toJSONString(extra));
String key1 = "Sheet1!A1";
String key2 = "Sheet2!A1";
switch (extra.getType()) {
case COMMENT:
log.info("额外信息是批注,在rowIndex:{},columnIndex;{},内容是:{}", extra.getRowIndex(), extra.getColumnIndex(),
extra.getText());
break;
case HYPERLINK:
if (key1.equals(extra.getText())) {
log.info("额外信息是超链接,在rowIndex:{},columnIndex;{},内容是:{}",
extra.getRowIndex(), extra.getColumnIndex(), extra.getText());
} else if (key2.equals(extra.getText())) {
log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{},"
+ "内容是:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex(), extra.getText());
} else {
throw new BizException("Unknown hyperlink!");
}
break;
case MERGE:
log.info("额外信息是超链接,而且覆盖了一个区间,在firstRowIndex:{},firstColumnIndex;{},lastRowIndex:{},lastColumnIndex:{}",
extra.getFirstRowIndex(), extra.getFirstColumnIndex(), extra.getLastRowIndex(),
extra.getLastColumnIndex());
break;
default:
}
}
/**
* 当处理数据时出现异常时的处理方式,是记录异常还是抛出异常由自己决定
*
* @param exception 异常
* @param context 上下文
* @throws Exception 抛出异常信息
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合invokeHeadMap使用
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
log.info("第 {} 行,第 {} 列解析异常,数据为:{}",
excelDataConvertException.getRowIndex(), excelDataConvertException.getColumnIndex(),
excelDataConvertException.getCellData().getData());
}
throw exception;
}
/**
* 只处理指定行数的数据,防止数据量过大,当行数超过指定的最大行数时,返回false,即下面的数据不再处理
*
* @param context 上下文
* @return 返回值,如果超过最大行数,则返回false,就不会继续执行了
*/
@Override
public boolean hasNext(AnalysisContext context) {
ReadRowHolder readRowHolder = context.readRowHolder();
Integer row = readRowHolder.getRowIndex();
return row <= maxRow;
}
/**
* 获取单元格内容
*
* @param rowIndex 当前行数
* @param columnIndex 当前列
* @param title 单元格表头
* @param readCellData 单元格
* @return 返回值
*/
protected String getCellValue(Integer rowIndex, Integer columnIndex, String title, ReadCellData<String> readCellData) {
String value = "";
CellDataTypeEnum cellDataTypeEnum = readCellData.getType();
if (CellDataTypeEnum.STRING.equals(cellDataTypeEnum)) {
String object = readCellData.getStringValue();
value = String.valueOf(object);
} else if (CellDataTypeEnum.NUMBER.equals(cellDataTypeEnum)) {
BigDecimal object = readCellData.getNumberValue();
DataFormatData formatData = readCellData.getDataFormatData();
Short formatDataIndex = formatData.getIndex();
String format = formatData.getFormat();
// 判断是否是日期格式
if (DateUtils.isADateFormat(formatDataIndex, format)) {
value = this.formatDate(object);
} else {
value = this.formatNumber(object);
}
} else if (CellDataTypeEnum.BOOLEAN.equals(cellDataTypeEnum)) {
Boolean object = readCellData.getBooleanValue();
value = String.valueOf(object);
} else {
log.info("字段类型为:{},当前行数为:{},当前列为:{},表头为:{},字段值为:{}",
cellDataTypeEnum, rowIndex, columnIndex, title, JSON.toJSONString(readCellData));
}
return value;
}
/**
* 格式化数字,单元格读出来的数字有可能是科学计数法,所以此处需要转换
*
* @param object 入参单元格数字类型内容
* @return 返回值
*/
protected String formatNumber(Object object) {
NumberFormat numberFormat = NumberFormat.getInstance();
String result = numberFormat.format(object);
// 这种方法对于自动加".0"的数字可直接解决
// 但如果是科学计数法的数字就转换成了带逗号的,例如:12345678912345的科学计数法是1.23457E+13,
// 经过这个格式化后就变成了字符串“12,345,678,912,345”,这也并不是想要的结果,所以要将逗号去掉
String code = ",";
if (result.contains(code)) {
result = result.replace(code, "");
}
return result;
}
/**
* 格式化日期,Excel中的日期记录的是一个double类型的数值,表示从1900年开始到现在所经过的天数,小数表示时间,所以需要将其转换为JAVA时间,然后格式化成字符串
*
* @param object 入参单元格日期类型内容
* @return 返回值
*/
protected String formatDate(Object object) {
double excelDate = Double.parseDouble(String.valueOf(object));
// 将Excel中保存的double类型的时间转换为字符串
Date date = org.apache.poi.ss.usermodel.DateUtil
.getJavaDate(excelDate, false);
return DateUtil.dateToStr(date);
}
/**
* 格式化字符串,将表头中的特殊字符去掉
*
* @param origin 原始字符串
* @return 返回值
*/
protected String formatString(String origin) {
return origin.replace("*", "")
.replace(",", "")
.replace(",", "")
;
}
}
2.4 读取文件
// 最多读取 MAX_ROW 行内容
ExcelListener listener = new ExcelListener(MAX_ROW);
List<DemoExportDto> list = EasyExcel.read(excelInputStream, DemoExportDto.class, listener)
// 忽略空行
.ignoreEmptyRow(Boolean.TRUE)
// 读取第一个sheet页
.sheet(0)
// 表头行数
.headRowNumber(2)
// 自动Trim
.autoTrim(Boolean.TRUE)
.doReadSync();
// 获取表头中文数据
Map<Integer, String> tableHeadMap = listener.getTableHeadMap();
// 获取Excel中的单据内容
List<Map<String, String>> content = listener.getContent();
3、POM依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.2</version>
</dependency>
|