EasyExcel 初使用—— Java 实现读取 Excel 功能

'# EasyExcel 初使用—— Java 实现读取 Excel 功能

一、背景与问题

在企业级开发中,Excel 文件的读取和处理是常见需求。传统做法通常使用 Apache POI,但其存在以下痛点:

  • 内存占用高(加载整个 Excel 文件到内存)
  • 处理大数据量时易触发 OutOfMemoryError
  • 代码复杂度高(需要处理 Workbook、Sheet、Row 等对象)
  • 对 Excel 2007+ 的 xlsx 格式支持不完善

EasyExcel 作为阿里巴巴开源的 Excel 处理库,通过以下创新解决了上述问题:

  1. 基于 SAX 模式逐行读取,避免内存溢出
  2. 支持大文件处理(可处理超过 100M 的 Excel 文件)
  3. 提供丰富的数据转换和校验机制
  4. 支持多种格式(xls/xlsx/et)和多种数据类型(日期/数字/字符串)

在实际项目中,我们常需要处理用户导入导出功能,比如订单数据导入、报表统计等场景。EasyExcel 的设计正好契合这些需求。

二、基本原理

1. 核心架构

EasyExcel 的核心架构分为三个层次:

  • 数据模型层:定义 Java Bean 与 Excel 单元格的映射关系
  • 解析层:基于 SAX 模式逐行解析 Excel 文件
  • 处理层:支持数据转换、校验、回调等业务逻辑

其核心优势在于通过 SAX 解析 实现流式处理,避免一次性加载整个文件到内存。这种设计使得 EasyExcel 能轻松处理数百万行数据。

2. 工作机制

EasyExcel 通过以下流程读取 Excel 文件:

  1. 创建 ExcelReader 对象,指定文件路径和数据模型
  2. 通过监听器(ReadListener)逐行处理数据
  3. 使用反射机制将单元格值转换为 Java 对象属性
  4. 支持自定义校验规则(如必填字段校验、格式校验)

其关键在于使用 SAX 解析器(Simple API for XML),这与传统 DOM 解析(一次性加载整个 XML)形成鲜明对比。

三、环境准备

1. 依赖引入

在 Maven 项目中添加以下依赖:

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

2. 开发环境

  • JDK 1.8+
  • IDE:IntelliJ IDEA / Eclipse
  • 开发语言:Java

四、核心实现

1. 基础读取示例

public class EasyExcelDemo {
    public static void main(String[] args) {
        String fileName = "test.xlsx";
        
        try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            
            excelReader.read(readSheet, new PageReadListener<ExcelData>(dataList -> {
                for (ExcelData data : dataList) {
                    System.out.println("读取到数据:" + data);
                }
            }));
        } catch (Exception e) {
            System.err.println("读取Excel时发生异常:" + e.getMessage());
        }
    }
}

关键代码解释:

  • EasyExcel.read(fileName).build() 创建 ExcelReader 实例
  • readSheet(0) 指定读取第一个工作表
  • PageReadListener 实现分页读取,避免内存溢出
  • try-with-resources 确保资源释放

2. 带头读取示例

public class HeaderReadDemo {
    public static void main(String[] args) {
        String fileName = "header.xlsx";
        
        EasyExcel.read(fileName, ExcelData.class, new PageReadListener<ExcelData>(dataList -> {
            for (ExcelData data : dataList) {
                System.out.println("读取到数据:" + data);
            }
        })).sheet().doRead();
    }
}

关键代码解释:

  • ExcelData.class 指定数据模型类
  • sheet() 方法指定读取的工作表(默认第一个)
  • 自动识别表头,将第一行作为字段映射

3. 自定义监听器示例

public class CustomReadListener implements ReadListener<ExcelData> {
    @Override
    public void invoke(ExcelData data, AnalysisContext context) {
        System.out.println("处理数据:" + data);
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) {
        System.err.println("读取异常:" + exception.getMessage());
    }

    @Override
    public void doReadFinish(boolean readAllSuccess, AnalysisContext context) {
        System.out.println("读取完成,共读取" + readAllSuccess ? "成功" : "失败");
    }
}

关键代码解释:

  • invoke 方法处理每行数据
  • onException 处理异常
  • doReadFinish 读取完成后回调

五、完整案例

1. 业务场景

假设需要读取用户导入的订单数据,包含以下字段:

  • 订单号(String)
  • 产品名称(String)
  • 数量(Integer)
  • 价格(Double)
  • 创建时间(LocalDateTime)

2. 数据模型类

public class OrderData {
    @ExcelProperty(index = 0)
    private String orderId;
    
    @ExcelProperty(index = 1)
    private String productName;
    
    @ExcelProperty(index = 2)
    private int quantity;
    
    @ExcelProperty(index = 3)
    private double price;
    
    @ExcelProperty(index = 4)
    private LocalDateTime createTime;
    
    // Getter 和 Setter 方法
}

3. 完整读取流程

public class OrderReadDemo {
    public static void main(String[] args) {
        String fileName = "orders.xlsx";
        
        EasyExcel.read(fileName, OrderData.class, new PageReadListener<OrderData>(dataList -> {
            for (OrderData data : dataList) {
                System.out.println("订单号:" + data.getOrderId() + 
                                  ", 产品:" + data.getProductName() +
                                  ", 数量:" + data.getQuantity());
            }
        })).sheet().doRead();
    }
}

关键点说明:

  • 使用 @ExcelProperty 注解定义字段映射关系
  • 自动处理日期格式转换(需配合 @DateTimeFormat)
  • 支持自定义转换器(如自定义价格处理)

六、源码解析

1. 核心类分析

ExcelReader 是核心类,其内部使用 SAXParser 实现流式处理:

public class ExcelReader {
    private final SAXParser saxParser;
    
    public ExcelReader(String fileName) {
        this.saxParser = SAXParserFactory.newInstance().newSAXParser();
    }
    
    public void read(ReadSheet readSheet, ReadListener readListener) {
        // 实现 SAX 解析逻辑
    }
}

2. 解析流程

EasyExcel 使用 ContentHandler 接口实现解析:

public class ExcelContentHandler implements ContentHandler {
    @Override
    public void startElement(String name, Attributes attributes) {
        // 处理开始标签
    }

    @Override
    public void endElement(String name) {
        // 处理结束标签
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        // 处理文本内容
    }
}

七、进阶使用

1. 多线程处理

public class MultiThreadReadDemo {
    public static void main(String[] args) {
        String fileName = "large_data.xlsx";
        
        List<ExcelReader> readers = new ArrayList<>();
        for (int i = 0; i < 4; i++) {
            readers.add(EasyExcel.read(fileName).build());
        }
        
        List<Thread> threads = new ArrayList<>();
        for (ExcelReader reader : readers) {
            Thread thread = new Thread(() -> {
                ReadSheet readSheet = EasyExcel.readSheet(0).build();
                reader.read(readSheet, new PageReadListener<ExcelData>(dataList -> {
                    // 处理数据
                }));
            });
            threads.add(thread);
        }
        
        for (Thread thread : threads) {
            thread.start();
        }
    }
}

2. 自定义转换器

public class CustomConverter implements Converter<String> {
    @Override
    public String convertToJavaType(Class<?> javaType, Object value) {
        return value.toString();
    }

    @Override
    public Object convertToExcelType(Class<?> javaType, Object value) {
        return value.toString();
    }
}

八、性能与工程实践

1. 性能优化方法

优化策略说明
分页读取默认每页 1000 条数据,可自定义页大小
多线程处理多线程并行处理数据
内存管理避免在监听器中缓存大量数据
异常处理通过 onException 捕获异常并处理

2. 安全风险分析

  • 文件格式风险:恶意构造的 Excel 文件可能引发内存泄漏
  • 数据类型转换风险:不当的转换可能导致数据丢失
  • XSS 攻击:直接输出用户输入内容可能造成注入攻击

3. 异常处理策略

public class SafeReadListener implements ReadListener<ExcelData> {
    @Override
    public void invoke(ExcelData data, AnalysisContext context) {
        try {
            // 处理数据逻辑
        } catch (Exception e) {
            System.err.println("处理数据时发生异常:" + e.getMessage());
        }
    }
    
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        System.err.println("读取异常:" + exception.getMessage());
    }
}

九、常见问题与踩坑

1. 常见错误

错误类型原因解决方案
内存溢出大文件未分页读取使用分页读取
数据类型错误字段类型不匹配使用 @ExcelProperty 明确类型
文件格式错误使用了不支持的 Excel 格式检查文件扩展名和内容
读取失败文件路径错误检查文件路径和权限

2. 典型问题

问题1:读取空文件时抛出异常

// 错误代码
EasyExcel.read("empty.xlsx").sheet().doRead();

解决方案:

// 正确代码
EasyExcel.read("empty.xlsx").sheet().doRead();

问题2:未正确关闭资源

// 错误代码
ExcelReader reader = EasyExcel.read(fileName).build();
reader.read(...);

解决方案:

// 正确代码
try (ExcelReader reader = EasyExcel.read(fileName).build()) {
    reader.read(...);
}

十、最佳实践

1. 推荐方案

  1. 分页读取:始终使用分页机制处理大数据
  2. 数据校验:在 invoke 方法中加入数据校验逻辑
  3. 异常处理:实现完整的异常处理链
  4. 资源管理:使用 try-with-resources 管理资源
  5. 性能监控:监控读取速度和内存使用情况

2. 避免错误做法

  1. 避免一次性加载全部数据:可能导致内存溢出
  2. 避免直接输出原始数据:防止 XSS 攻击
  3. 避免硬编码文件路径:使用配置管理
  4. 避免未处理的异常:可能导致程序崩溃

十一、总结

EasyExcel 作为 Java 中处理 Excel 文件的优秀工具,通过 SAX 解析机制实现了高效、安全的数据读取。其核心优势在于:

  • 避免内存溢出的流式处理
  • 支持大数据量处理
  • 提供灵活的校验和转换机制
  • 丰富的扩展能力

在实际开发中,建议:

  • 对大数据量处理使用 EasyExcel
  • 对小规模数据可考虑 POI
  • 重视异常处理和资源管理
  • 配合配置中心管理文件路径

通过合理使用 EasyExcel,可以显著提升 Excel 数据处理的效率和安全性,同时避免传统方法带来的诸多问题。

最后修改于:2026年09月28日 06:11

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日