在Java处理Excel文件的生态中,EasyExcel一度成为广大开发者的主流选择。然而,随着阿里巴巴官方宣布停止维护该项目,这一广受欢迎的开源工具逐渐陷入停滞状态。在此关键时刻,原项目作者挺身而出,承接后续维护工作,并在原有代码基础上进行深度重构与性能优化,最终推出全新升级版本——
FastExcel,旨在为开发者提供更高效、更稳定且持续迭代的Excel操作解决方案。
FastExcel是一款专注于实现高性能Excel读写操作的Java类库。作为EasyExcel的精神继承者,它不仅保留了原有的API设计风格,还在内存管理、执行效率以及功能拓展方面实现了全面增强。
官网地址:
readmex.com/fast-excel/
GitHub仓库:
github.com/fast-excel/fastexcel
| 特性 | 技术优势 | 业务价值 |
|---|---|---|
| 高性能读写 | 采用优化后的内存模型与处理算法 | 轻松应对百万级数据导入导出需求 |
| 流式处理 | 基于SAX模式逐行解析,避免全量加载 | 极大降低JVM内存压力,有效防止OOM异常 |
| 简洁API | 支持链式调用与注解驱动编程 | 提升开发效率,降低学习门槛 |
| 格式丰富 | 完整覆盖Excel各类样式与数据格式 | 满足报表、财务等复杂业务场景要求 |
使用Maven构建项目时,引入以下依赖:
<dependency> <groupId>cn.idev.excel</groupId> <artifactId>fastexcel</artifactId> <version>1.2.0</version> <!-- 建议使用最新版本以获取更多功能和修复 --> </dependency>
若采用Gradle,则添加如下配置:
implementation 'cn.idev.excel:fastexcel:1.2.0'
以下是一个典型的图书信息实体类示例,用于映射Excel中的字段:
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.format.DateTimeFormat;
import cn.idev.excel.annotation.format.NumberFormat;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 图书信息数据模型
* 用于Excel导入导出的数据映射
*/
@Data
public class Book {
@ExcelProperty(value = "书名", index = 0)
private String bookName;
@ExcelProperty(value = "作者", index = 1)
private String author;
@ExcelProperty(value = "价格", index = 2)
@NumberFormat("#,###.00") // 千分位分隔,保留两位小数
private BigDecimal price;
@ExcelProperty(value = "上架时间", index = 3)
@DateTimeFormat("yyyy-MM-dd") // 日期格式化
private Date saleDate;
// 可扩展构造方法或业务逻辑方法...
}
关键注解说明:
@ExcelProperty@NumberFormat@DateTimeFormat基础读取操作示例如下:
@Test
void testReadExcelBasic() throws FileNotFoundException {
File excelFile = ResourceUtils.getFile("classpath:excels/book-data.xlsx");
List<Book> bookList = new ArrayList<>();
FastExcel.read(excelFile, Book.class, new ReadListener<Book>() {
private static final int BATCH_SIZE = 100;
@Override
public void invoke(Book book, AnalysisContext context) {
// 每解析一行数据触发一次回调
System.out.println("解析到数据: " + JSON.toJSONString(book));
}
}).sheet().doRead();
}
@Test
void testWriteExcelBasic() {
// 创建模拟数据集
List<Book> bookList = generateSampleData(100);
// 执行导出操作
String outputPath = "generated-excels/books-export.xlsx";
FastExcel.write(outputPath, Book.class)
.sheet("图书列表") // 指定工作表名称
.doWrite(bookList); // 写入数据到文件
System.out.println("Excel文件生成成功: " + outputPath);
}
/**
* 生成示例数据用于测试
*/
| 参数 | 类型 | 说明 | 是否必填 |
|---|---|---|---|
| Excel文件对象 | File 或 InputStream | 指定要解析的Excel文件路径或输入流 | 是 |
| 数据映射模型类 | Class<T> | 用于定义Excel每行数据与Java对象之间的映射关系 | 是 |
| 数据读取监听器 | ReadListener<T> | 处理每一条解析后的数据记录,支持批量处理和异常捕获 | 是 |
@Test
void testReadExcelAdvanced() throws FileNotFoundException {
// 加载多Sheet的Excel文件
File excelFile = ResourceUtils.getFile("classpath:excels/multi-sheet-data.xlsx");
// 构建读取器实例
ExcelReaderBuilder readerBuilder = FastExcel.read(excelFile, Book.class);
// 读取特定名称的工作表
readerBuilder.sheet("图书数据") // 按Sheet名读取
.headRowNumber(2) // 声明前两行为表头
.doRead(new SimpleReadListener<>());
// 一次性读取所有Sheet
readerBuilder.doReadAll(new SimpleReadListener<>());
}
/**
* 简化数据读取监听器实现
*/
private static class SimpleReadListener<T> implements ReadListener<T> {
private final List<T> dataList = new ArrayList<>();
@Override
public void invoke(T data, AnalysisContext context) {
// 收集每条解析成功的数据
dataList.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 全部读取完成后回调
System.out.println("读取完成,数据量: " + dataList.size());
}
}
/**
* 对收集的数据进行分批处理(可用于数据库批量插入等场景)
*/
private void processBatchData(List<Book> batchData) {
// 模拟业务处理过程
System.out.println("批量处理 " + batchData.size() + " 条数据");
// 实际应用中可调用服务层方法执行持久化操作
// bookService.batchSave(batchData);
}
@Override
public void invoke(Book book, AnalysisContext context) {
// 添加当前行数据至缓存列表
bookList.add(book);
// 达到设定批次大小时触发处理
if (bookList.size() % BATCH_SIZE == 0) {
processBatchData(bookList);
bookList.clear(); // 清空已处理数据
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 解析结束后的收尾工作
System.out.println("Excel解析完成,总计处理: " + bookList.size() + " 条数据");
// 处理最后不足一个批次的剩余数据
if (!bookList.isEmpty()) {
processBatchData(bookList);
}
}
@Override
public void onException(Exception exception, AnalysisContext context) {
// 异常情况下的日志输出与错误抛出
System.err.println("解析异常: " + exception.getMessage());
throw new RuntimeException("Excel解析失败", exception);
}
}).sheet().doRead();
private List<Book> generateSampleData(int count) {
List<Book> books = new ArrayList<>();
Random random = new Random();
for (int i = 1; i <= count; i++) {
Book book = new Book();
book.setBookName("Java编程思想 " + i);
book.setAuthor("作者" + (char)('A' + random.nextInt(26)));
book.setPrice(new BigDecimal("98." + random.nextInt(99)));
book.setSaleDate(new Date(System.currentTimeMillis() -
random.nextInt(365) * 24 * 60 * 60 * 1000L));
books.add(book);
}
return books;
}
通过以下测试方法展示如何使用模板进行复杂数据写入操作:
@Test
void testWriteExcelAdvanced() {
List<Book> bookList = generateSampleData(50);
String templatePath = "templates/book-template.xlsx";
String outputPath = "generated-excels/books-with-template.xlsx";
try {
FastExcel.write(outputPath, Book.class)
.withTemplate(templatePath)
.sheet()
.includeColumnFieldNames(Arrays.asList("bookName", "author", "price", "saleDate"))
.doWrite(bookList);
} catch (Exception e) {
FastExcel.write(outputPath, Book.class)
.sheet()
.doWrite(bookList);
}
}
针对大量数据的导出场景,可采用分页写入方式避免内存溢出:
@Test
void testWriteLargeData() {
List<Book> largeData = generateSampleData(100000);
String outputPath = "generated-excels/large-books-data.xlsx";
FastExcel.write(outputPath, Book.class)
.autoCloseStream(true)
.sheet("第一页")
.doWrite(largeData.subList(0, 50000));
}
以下是常见的写入配置方法及其应用场景:
| 配置方法 | 作用 | 使用场景 |
| 设置Sheet名称 | 为工作表指定自定义名称 | 多Sheet导出 |
| 使用模板文件 | 保持与原始文件一致的样式和格式 | 保持样式一致性 |
| 自动关闭流 | 确保资源在写入完成后及时释放 | 资源管理 |
| 排除字段 | 忽略某些不需要导出的属性 | 选择性导出 |
| 包含字段 | 仅导出指定的字段列表 | 选择性导出 |
处理大体积Excel文件时,合理的内存控制至关重要。以下是一个服务类示例,用于高效读取大型表格文件:
/**
* 大文件读取优化方案
*/
@Service
public class LargeExcelProcessor {
private static final int BATCH_PROCESS_SIZE = 1000;
public void processLargeExcel(File excelFile) {
AtomicInteger counter = new AtomicInteger(0);
List<Book> batchBuffer = new ArrayList<>(BATCH_PROCESS_SIZE);
FastExcel.read(excelFile, Book.class, new ReadListener<Book>() {
@Override
public void invoke(Book data, AnalysisContext context) {
batchBuffer.add(data);
if (batchBuffer.size() >= BATCH_PROCESS_SIZE) {
private void processAndClearBuffer(List<Book> buffer, AtomicInteger counter) {
try {
// 执行实际的业务逻辑:如批量保存至数据库或发送到消息队列
bookService.batchSave(buffer);
counter.addAndGet(buffer.size());
} finally {
buffer.clear(); // 确保缓冲区被及时清空,防止内存堆积
}
}
@Override
public void invoke(Book data, AnalysisContext context) {
batchBuffer.add(data);
if (batchBuffer.size() >= BATCH_SIZE) {
processAndClearBuffer(batchBuffer, counter);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if (!batchBuffer.isEmpty()) {
processAndClearBuffer(batchBuffer, counter);
}
System.out.println("处理完成,总计: " + counter.get() + " 条记录");
}
}).sheet().doRead();
为了提升数据读取过程中的健壮性,可对导入内容进行前置校验,并统一捕获异常信息。以下为增强型 Excel 读取处理器的实现示例:
/**
* 增强型Excel读取处理器
*/
@Component
public class EnhancedExcelReader {
public ExcelImportResult<Book> readWithValidation(File excelFile) {
ExcelImportResult<Book> result = new ExcelImportResult<>();
FastExcel.read(excelFile, Book.class, new ReadListener<Book>() {
@Override
public void invoke(Book data, AnalysisContext context) {
// 对每条数据执行校验
ValidationResult validation = validateBookData(data);
if (validation.isValid()) {
result.addSuccessData(data);
} else {
result.addErrorData(data, validation.getErrors());
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
result.setCompleted(true); @ExcelProperty
}
@Override
public void onException(Exception exception, AnalysisContext context) {
result.setHasError(true);
result.setErrorMessage(exception.getMessage());
}
}).sheet().doRead();
return result;
}
/**
* 校验图书数据的有效性
*/
private ValidationResult validateBookData(Book book) {
ValidationResult result = new ValidationResult();
if (book.getPrice() == null || book.getPrice().compareTo(BigDecimal.ZERO) < 0) {
result.addError("价格必须大于0");
}
if (book.getSaleDate() == null || book.getSaleDate().after(new Date())) {
result.addError("上架时间不能晚于当前时间");
}
return result;
}
}
从 EasyExcel 平滑迁移到 FastExcel 的关键步骤如下:
在项目构建配置中替换原有依赖:
<!-- 原始EasyExcel依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>${easyexcel.version}</version>
</dependency>
<!-- 替换为FastExcel -->
<dependency>
<groupId>cn.idev.excel</groupId>
@ExcelProperty
<dependency>
<groupId>cn.idev</groupId>
<artifactId>fastexcel</artifactId>
<version>${fastexcel.version}</version>
</dependency>
// 旧版本导入路径
import com.alibaba.excel.*;
import com.alibaba.excel.annotation.*;
// 更新后的导入路径
import cn.idev.excel.*;
import cn.idev.excel.annotation.*;
@ExcelProperty
新启动项目:推荐直接使用 FastExcel,充分利用其稳定性和性能优势。
已有项目升级:可在评估影响后分阶段迁移,逐步享受持续迭代带来的好处。
大数据量处理场景:FastExcel 在大文件读写、低内存消耗方面表现更优,适合高性能需求。
FastExcel 在继承 EasyExcel 优秀架构的基础上,通过不断的技术打磨与优化,为 Java 平台下的 Excel 操作提供了更为高效可靠的解决方案,已成为当前环境下值得优先考虑的技术选型。
扫码加好友,拉您进群



收藏
