EasyExcel 与 Apache POI:Java 操作 Excel 的详解

发布于:2025-03-31 ⋅ 阅读:(30) ⋅ 点赞:(0)

在 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 操作开发。