在 Java 开发中,操作 Excel 文件是一个常见的需求。无论是数据导入、导出,还是报表生成,掌握高效的 Excel 操作库都能大大提高开发效率。本文将深入探讨两个流行的 Java Excel 操作库:EasyExcel 和 Apache POI,从原理、使用方法到高级技巧,进行全面的讲解。文章内容丰富,字数达到 1 万字,旨在为读者提供全面而深入的知识。
一、EasyExcel 详解
1. 简介
EasyExcel 是阿里巴巴开源的一个基于 Java 的 Excel 操作库。它在传统的 Excel 操作库(如 Apache POI)基础上进行了封装和优化,提供了更简洁的 API 和更高的性能,尤其是在处理大数据量的 Excel 文件时表现出色。
2. 主要特点
- 高性能:采用 SAX(Simple API for XML)方式读取 Excel 文件,逐行读取数据,避免将整个文件加载到内存中,从而有效处理大文件。
- 简单易用:提供简洁的 API,减少重复代码,使 Excel 操作更加直观和便捷。
- 丰富的功能:支持 Excel 的读取、写入、格式化、数据验证等功能。
3. 快速入门
Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel-spring-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
读取 Excel
public class ExcelReadDemo {
public static void main(String[] args) {
String fileName = "path/to/your/file.xlsx";
ExcelReader excelReader = EasyExcelFactory.getExcelReader(fileName);
excelReader.read(new AnalysisEventListener<UserData>() {
@Override
public void invoke(UserData data, AnalysisContext context) {
System.out.println(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("Read finished");
}
}, new ExcelListener());
}
}
写入 Excel
public class ExcelWriteDemo {
public static void main(String[] args) {
String fileName = "path/to/your/file.xlsx";
ExcelWriter excelWriter = EasyExcelFactory.getWriterFactory().build(fileName, null, null);
WriteSheet writeSheet = EasyExcelFactory.getWriteSheet(0, "模板", null);
excelWriter.write(getData(), writeSheet);
excelWriter.close();
}
private static List<UserData> getData() {
List<UserData> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
UserData data = new UserData();
data.setName("name" + i);
data.setAge(i);
list.add(data);
}
return list;
}
}
4. 高级功能
数据格式化
@ExcelProperty(value = "日期", format = "yyyy-MM-dd")
private Date date;
数据验证
@ExcelProperty(value = "年龄")
@Min(value = 0, message = "年龄不能小于0")
private Integer age;
动态头
List<String> head = new ArrayList<>();
head.add("姓名");
head.add("年龄");
WriteSheet writeSheet = EasyExcelFactory.getWriteSheet(0, "模板", head);
5. 性能优化
内存优化
在处理大数据量时,EasyExcel 的 SAX 模式本身已经很高效,但可以通过调整批量读取的大小进一步优化内存使用:
AnalysisEventListener listener = new AnalysisEventListener() {
// 批量处理大小
private static final int BATCH_COUNT = 1000;
private int batchSize = 0;
@Override
public void invoke(Object object, AnalysisContext context) {
// 批量处理逻辑
batchSize++;
if (batchSize >= BATCH_COUNT) {
// 执行批量操作,如批量插入数据库
batchSize = 0;
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 处理剩余数据
}
};
多线程处理
对于非常大的数据集,可以结合多线程技术进一步提升处理速度:
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future<?>> futureList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Future<?> future = executorService.submit(() -> {
// 数据处理逻辑
});
futureList.add(future);
}
// 等待所有线程完成
for (Future<?> future : futureList) {
future.get();
}
executorService.shutdown();
6. 实际应用案例
电商订单数据处理
在电商系统中,经常需要处理大量的订单数据导入和导出。使用 EasyExcel 可以高效地完成这些任务:
// 订单数据导入
public void importOrderData(String fileName) {
ExcelReader excelReader = EasyExcelFactory.getExcelReader(fileName);
excelReader.read(new AnalysisEventListener<OrderData>() {
@Override
public void invoke(OrderData data, AnalysisContext context) {
// 验证和处理订单数据
if (isValidOrderData(data)) {
// 保存到数据库
orderService.saveOrder(data);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
System.out.println("订单数据导入完成");
}
}, new ExcelListener());
}
// 订单数据导出
public void exportOrderData(String fileName, List<OrderData> orderDataList) {
ExcelWriter excelWriter = EasyExcelFactory.getWriterFactory().build(fileName, null, null);
WriteSheet writeSheet = EasyExcelFactory.getWriteSheet(0, "订单数据", null);
excelWriter.write(orderDataList, writeSheet);
excelWriter.close();
}
财务报表生成
在财务系统中,需要定期生成各种报表。EasyExcel 可以方便地生成格式化的报表:
public void generateFinancialReport(String fileName, FinancialReportData reportData) {
ExcelWriter excelWriter = EasyExcelFactory.getWriterFactory().build(fileName, null, null);
WriteSheet writeSheet = EasyExcelFactory.getWriteSheet(0, "财务报表", null);
// 设置表头
List<List<String>> head = new ArrayList<>();
List<String> headerRow = new ArrayList<>();
headerRow.add("项目");
headerRow.add("金额");
headerRow.add("日期");
head.add(headerRow);
// 设置数据
List<List<Object>> data = new ArrayList<>();
for (FinancialItem item : reportData.getItems()) {
List<Object> row = new ArrayList<>();
row.add(item.getProjectName());
row.add(item.getAmount());
row.add(item.getDate());
data.add(row);
}
// 写入表头和数据
excelWriter.write(head, writeSheet);
excelWriter.write(data, writeSheet);
excelWriter.close();
}
二、Apache POI 详解
1. 简介
Apache POI 是一个开源的 Java 库,用于操作各种 Microsoft Office 格式的文件,包括 Excel、Word、PowerPoint 等。它提供了低级 API 和高层 API,可以进行文件的读取、写入、创建、修改等操作。
2. 主要特点
- 支持多种文件格式:支持 Excel、Word、PowerPoint、Publisher 等多种 Microsoft Office 文件格式。
- 功能强大:可以进行文件的读取、写入、创建、修改等操作,支持复杂的文件结构和格式。
- 跨平台:可以在任何支持 Java 的平台上运行。
3. 快速入门
Maven 依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
读取 Excel
public class ExcelReadDemo {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("path/to/your/file.xlsx");
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
for (Cell cell : row) {
System.out.print(cell.toString() + "\t");
}
System.out.println();
}
workbook.close();
}
}
写入 Excel
public class ExcelWriteDemo {
public static void main(String[] args) throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("Hello, World!");
FileOutputStream fileOutputStream = new FileOutputStream("path/to/your/file.xlsx");
workbook.write(fileOutputStream);
workbook.close();
}
}
4. 高级功能
设置单元格样式
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
font.setBold(true);
cellStyle.setFont(font);
cell.setCellStyle(cellStyle);
数据格式化
CellStyle cellStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
cell.setCellStyle(cellStyle);
处理大数据量
// 使用 SAX 方式读取大数据量 Excel 文件
XMLReader xmlReader = new SAXParser().getXMLReader();
XLSXSheetXMLHandler handler = new XLSXSheetXMLHandler(workbook, null, null);
xmlReader.setContentHandler(handler);
xmlReader.parse(new InputSource(new FileReader("path/to/your/file.xlsx")));
5. 性能优化
内存优化
在处理大数据量时,可以采用分批读取和写入的方式,减少内存占用:
// 分批读取
public void processLargeExcelFile(String fileName) throws IOException {
FileInputStream fileInputStream = new FileInputStream(fileName);
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
int batchSize = 1000;
int startRow = 0;
int totalRows = sheet.getPhysicalNumberOfRows();
while (startRow < totalRows) {
int endRow = Math.min(startRow + batchSize, totalRows);
for (int rowNum = startRow; rowNum < endRow; rowNum++) {
Row row = sheet.getRow(rowNum);
// 处理行数据
}
startRow = endRow;
}
workbook.close();
}
// 分批写入
public void writeLargeExcelFile(String fileName, List<List<Object>> data) throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1");
int batchSize = 1000;
for (int i = 0; i < data.size(); i++) {
Row row = sheet.createRow(i);
List<Object> rowData = data.get(i);
for (int j = 0; j < rowData.size(); j++) {
Cell cell = row.createCell(j);
cell.setCellValue(rowData.get(j).toString());
}
if ((i + 1) % batchSize == 0 || i == data.size() - 1) {
// 批量写入到文件
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
workbook.write(fileOutputStream);
fileOutputStream.close();
}
}
workbook.close();
}
多线程处理
对于非常大的数据集,可以结合多线程技术进一步提升处理速度:
ExecutorService executorService = Executors.newFixedThreadPool(4);
List<Future<?>> futureList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
Future<?> future = executorService.submit(() -> {
// 数据处理逻辑
});
futureList.add(future);
}
// 等待所有线程完成
for (Future<?> future : futureList) {
future.get();
}
executorService.shutdown();
6. 实际应用案例
学生成绩管理系统
在教育系统中,需要处理学生的成绩数据。使用 Apache POI 可以方便地导入和导出成绩数据:
// 成绩数据导入
public void importStudentScores(String fileName) throws IOException {
FileInputStream fileInputStream = new FileInputStream(fileName);
Workbook workbook = new XSSFWorkbook(fileInputStream);
Sheet sheet = workbook.getSheetAt(0);
for (Row row : sheet) {
if (row.getRowNum() == 0) {
// 跳过表头
continue;
}
StudentScore score = new StudentScore();
score.setStudentId((Long) row.getCell(0).getNumericCellValue());
score.setStudentName(row.getCell(1).getStringCellValue());
score.setSubject(row.getCell(2).getStringCellValue());
score.setScore((Double) row.getCell(3).getNumericCellValue());
// 验证和保存数据
if (isValidScore(score)) {
scoreService.saveScore(score);
}
}
workbook.close();
}
// 成绩数据导出
public void exportStudentScores(String fileName, List<StudentScore> scores) throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("成绩数据");
// 设置表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("学生ID");
headerRow.createCell(1).setCellValue("学生姓名");
headerRow.createCell(2).setCellValue("科目");
headerRow.createCell(3).setCellValue("成绩");
// 设置数据
for (int i = 0; i < scores.size(); i++) {
StudentScore score = scores.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(score.getStudentId());
row.createCell(1).setCellValue(score.getStudentName());
row.createCell(2).setCellValue(score.getSubject());
row.createCell(3).setCellValue(score.getScore());
}
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
workbook.write(fileOutputStream);
workbook.close();
}
企业资源规划系统
在企业资源规划(ERP)系统中,需要处理各种业务数据,如库存、销售、采购等。Apache POI 可以用于生成和处理这些数据的 Excel 报表:
public void generateInventoryReport(String fileName, List<InventoryItem> inventoryItems) throws IOException {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("库存报表");
// 设置表头
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("产品ID");
headerRow.createCell(1).setCellValue("产品名称");
headerRow.createCell(2).setCellValue("库存数量");
headerRow.createCell(3).setCellValue("最后更新时间");
// 设置数据
for (int i = 0; i < inventoryItems.size(); i++) {
InventoryItem item = inventoryItems.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(item.getProductId());
row.createCell(1).setCellValue(item.getProductName());
row.createCell(2).setCellValue(item.getQuantity());
row.createCell(3).setCellValue(item.getLastUpdateTime());
}
// 设置单元格样式
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
for (int i = 0; i < 4; i++) {
sheet.getRow(0).getCell(i).setCellStyle(headerStyle);
}
// 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
FileOutputStream fileOutputStream = new FileOutputStream(fileName);
workbook.write(fileOutputStream);
workbook.close();
}
三、总结
EasyExcel 与 Apache POI 的对比
性能方面
- EasyExcel:在处理大数据量时表现出色,采用 SAX 方式读取,内存占用低。
- Apache POI:在处理小到中等规模数据时性能尚可,但处理大数据量时内存占用较高。
易用性方面
- EasyExcel:API 简洁,注解驱动,减少重复代码,易于上手。
- Apache POI:API 较为复杂,需要更多代码来实现相同功能。
功能全面性方面
- EasyExcel:专注于 Excel 操作,功能覆盖读取、写入、格式化、验证等。
- Apache POI:支持多种 Microsoft Office 文件格式,功能更为全面。
适用场景选择
- EasyExcel:适合专注于 Excel 操作的场景,尤其是大数据量处理和高性能需求的场景。其 API 简洁,易于上手,推荐在新项目中优先考虑。
- Apache POI:适合需要操作多种 Microsoft Office 文件格式的场景,功能全面,但 API 较为复杂,性能在处理大数据量时不如 EasyExcel。
通过本文的详细讲解,希望读者能够根据项目需求,合理选择 EasyExcel 或 Apache POI,高效地进行 Java 中的 Excel 操作开发。