Java 读取 Excel 文件

Java 读取 Excel 文件

      • 一、前置准备:引入依赖
        • [方案 1:Apache POI(功能全,兼容所有Excel版本)](#方案 1:Apache POI(功能全,兼容所有Excel版本))
        • [方案 2:EasyExcel(阿里开源,低内存,推荐大数据量)](#方案 2:EasyExcel(阿里开源,低内存,推荐大数据量))
      • [二、方案 1:Apache POI 读取 Excel(通用场景)](#二、方案 1:Apache POI 读取 Excel(通用场景))
        • [场景 1:读取所有sheet的所有单元格(基础版)](#场景 1:读取所有sheet的所有单元格(基础版))
        • [场景 2:读取指定sheet和指定行(精准读取)](#场景 2:读取指定sheet和指定行(精准读取))
      • [三、方案 2:EasyExcel 读取 Excel(高性能,推荐大数据量)](#三、方案 2:EasyExcel 读取 Excel(高性能,推荐大数据量))
        • [步骤 1:定义数据实体(与Excel表头映射)](#步骤 1:定义数据实体(与Excel表头映射))
        • [步骤 2:自定义监听器(处理读取到的数据)](#步骤 2:自定义监听器(处理读取到的数据))
        • [步骤 3:读取Excel文件(核心代码)](#步骤 3:读取Excel文件(核心代码))
      • 四、关键注意事项
        • [1. 文件路径与权限](#1. 文件路径与权限)
        • [2. 版本兼容](#2. 版本兼容)
        • [3. 性能优化](#3. 性能优化)
        • [4. 日期/数字格式](#4. 日期/数字格式)
      • 五、两种方案对比
      • 六、常见问题解决
        • [1. `FileNotFoundException`](#1. FileNotFoundException)
        • [2. 内存溢出(OOM)](#2. 内存溢出(OOM))
        • [3. 日期读取为数字](#3. 日期读取为数字)

Java 读取 Excel 文件核心依赖 Apache POI (兼容 .xls(Excel 97-2003)和 .xlsx(Excel 2007+))或 EasyExcel(阿里开源,低内存、高性能),以下是两种主流方案的完整实现,覆盖「读取简单单元格、读取指定sheet、读取表头+数据」等场景。

一、前置准备:引入依赖

方案 1:Apache POI(功能全,兼容所有Excel版本)

Maven 依赖(需同时引入 poipoi-ooxml,分别对应 .xls.xlsx):

xml 复制代码
<dependencies>
    <!-- 核心依赖:处理 .xls -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version> <!-- 推荐最新稳定版 -->
    </dependency>
    <!-- 处理 .xlsx -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version>
    </dependency>
    <!-- 可选:简化日期格式处理 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-schemas</artifactId>
        <version>4.1.2</version>
    </dependency>
</dependencies>
方案 2:EasyExcel(阿里开源,低内存,推荐大数据量)

Maven 依赖(仅需核心包,自动兼容 .xls/.xlsx):

xml 复制代码
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version> <!-- 最新版 -->
</dependency>
<!-- 可选:日志依赖(EasyExcel 依赖 slf4j) -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.9</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-simple</artifactId>
    <version>2.0.9</version>
</dependency>

二、方案 1:Apache POI 读取 Excel(通用场景)

场景 1:读取所有sheet的所有单元格(基础版)
java 复制代码
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.FileInputStream;
import java.io.IOException;

public class POIExcelReader {
    public static void main(String[] args) {
        String filePath = "D:/test.xlsx"; // 替换为你的Excel文件路径
        // 区分 .xls 和 .xlsx
        Workbook workbook = null;
        try (FileInputStream fis = new FileInputStream(filePath)) {
            if (filePath.endsWith(".xlsx")) {
                workbook = new XSSFWorkbook(fis); // .xlsx
            } else if (filePath.endsWith(".xls")) {
                workbook = new HSSFWorkbook(fis); // .xls
            } else {
                throw new IllegalArgumentException("不支持的Excel格式");
            }

            // 遍历所有sheet
            for (Sheet sheet : workbook) {
                System.out.println("===== Sheet名称:" + sheet.getSheetName() + " =====");
                // 遍历所有行(跳过表头:从第1行开始,rowNum=1)
                for (Row row : sheet) {
                    // 遍历该行所有单元格
                    for (Cell cell : row) {
                        // 获取单元格值(统一格式)
                        String cellValue = getCellValue(cell);
                        System.out.print(cellValue + "\t");
                    }
                    System.out.println(); // 换行
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (workbook != null) {
                try {
                    workbook.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 工具方法:统一处理不同类型的单元格值
    private static String getCellValue(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING: // 字符串
                return cell.getStringCellValue();
            case NUMERIC: // 数字/日期
                if (DateUtil.isCellDateFormatted(cell)) {
                    // 日期类型
                    return cell.getDateCellValue().toString();
                } else {
                    // 数字类型(避免科学计数法)
                    return String.valueOf(cell.getNumericCellValue());
                }
            case BOOLEAN: // 布尔值
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA: // 公式
                return cell.getCellFormula() + " = " + cell.getCachedFormulaResultType();
            case BLANK: // 空单元格
                return "";
            default:
                return "";
        }
    }
}
场景 2:读取指定sheet和指定行(精准读取)
java 复制代码
// 读取指定sheet(索引从0开始,或按名称)
Sheet sheet = workbook.getSheetAt(0); // 第一个sheet
// 或 Sheet sheet = workbook.getSheet("用户数据"); // 按名称

// 读取指定行(如第2行,rowNum=1)
Row targetRow = sheet.getRow(1);
if (targetRow != null) {
    // 读取指定单元格(如第3列,cellNum=2)
    Cell targetCell = targetRow.getCell(2);
    String value = getCellValue(targetCell);
    System.out.println("指定单元格值:" + value);
}

// 遍历有效行(跳过空行)
int lastRowNum = sheet.getLastRowNum(); // 最后一行索引
for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
    Row row = sheet.getRow(rowNum);
    if (row == null) {
        continue; // 跳过空行
    }
    // 读取该行单元格
    String name = getCellValue(row.getCell(0)); // 第1列:姓名
    String age = getCellValue(row.getCell(1));  // 第2列:年龄
    System.out.println("姓名:" + name + ",年龄:" + age);
}

三、方案 2:EasyExcel 读取 Excel(高性能,推荐大数据量)

EasyExcel 无需加载整个Excel到内存,适合读取十万级以上数据,核心是通过「监听器」逐行读取。

步骤 1:定义数据实体(与Excel表头映射)
java 复制代码
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

// 对应Excel的表头:姓名、年龄、手机号
@Data // Lombok注解,自动生成get/set
public class UserExcelDTO {
    // value:Excel表头名称,index:列索引(可选)
    @ExcelProperty(value = "姓名", index = 0)
    private String name;

    @ExcelProperty(value = "年龄", index = 1)
    private Integer age;

    @ExcelProperty(value = "手机号", index = 2)
    private String phone;
}
步骤 2:自定义监听器(处理读取到的数据)
java 复制代码
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;

// 自定义监听器,逐行读取数据并存储
public class UserExcelListener extends AnalysisEventListener<UserExcelDTO> {
    // 存储读取到的数据
    private List<UserExcelDTO> dataList = new ArrayList<>();

    // 每读取一行数据触发
    @Override
    public void invoke(UserExcelDTO user, AnalysisContext context) {
        dataList.add(user);
        System.out.println("读取到数据:" + user);
        // 可在此处批量处理(如每1000条插入数据库)
        if (dataList.size() >= 1000) {
            handleData(); // 处理数据
            dataList.clear(); // 清空
        }
    }

    // 所有数据读取完成后触发
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        handleData(); // 处理剩余数据
        System.out.println("Excel读取完成,总数据量:" + dataList.size());
    }

    // 数据处理逻辑(如插入数据库)
    private void handleData() {
        if (!dataList.isEmpty()) {
            // TODO: 批量插入数据库/业务处理
            System.out.println("批量处理" + dataList.size() + "条数据");
        }
    }

    // 获取读取到的所有数据
    public List<UserExcelDTO> getDataList() {
        return dataList;
    }
}
步骤 3:读取Excel文件(核心代码)
java 复制代码
import com.alibaba.excel.EasyExcel;

import java.util.List;

public class EasyExcelReader {
    public static void main(String[] args) {
        String filePath = "D:/test.xlsx";
        // 初始化监听器
        UserExcelListener listener = new UserExcelListener();

        // 读取Excel(指定文件路径、实体类、监听器)
        EasyExcel.read(filePath, UserExcelDTO.class, listener)
                .sheet("用户数据") // 指定sheet名称(可选,默认第一个)
                .headRowNumber(1) // 表头行数(默认1行)
                .doRead(); // 执行读取

        // 获取所有数据
        List<UserExcelDTO> dataList = listener.getDataList();
        System.out.println("最终读取到的数据:" + dataList);
    }
}

四、关键注意事项

1. 文件路径与权限
  • 确保文件路径无中文/空格(避免 FileNotFoundException);
  • 若读取服务器文件,需保证Java进程有文件读取权限(如Linux下 chmod 755)。
2. 版本兼容
  • .xls(HSSFWorkbook)最大支持65536行,.xlsx(XSSFWorkbook)无行数限制;
  • EasyExcel 自动兼容两种格式,无需手动区分。
3. 性能优化
  • Apache POI 读取大数据量Excel易内存溢出,需用 SXSSFWorkbook(流式读取);
  • EasyExcel 天生适合大数据量,无需额外配置。
4. 日期/数字格式
  • Apache POI 需手动判断日期格式(DateUtil.isCellDateFormatted);
  • EasyExcel 可通过 @ExcelProperty(converter = DateConverter.class) 自定义格式转换。

五、两种方案对比

方案 优点 缺点 适用场景
Apache POI 功能全、兼容所有Excel特性 大数据量易内存溢出、代码繁琐 小数据量、需操作复杂Excel(公式/宏)
EasyExcel 低内存、代码简洁、高性能 不支持宏/复杂公式 大数据量、普通数据读取(推荐)

六、常见问题解决

1. FileNotFoundException
  • 检查文件路径是否正确(绝对路径/相对路径);
  • 检查文件是否被占用(如Excel未关闭)。
2. 内存溢出(OOM)
  • Apache POI:改用 SXSSFWorkbook 流式读取;
  • 优先使用 EasyExcel。
3. 日期读取为数字
  • Apache POI:通过 DateUtil.isCellDateFormatted 判断并转换;
  • EasyExcel:配置日期转换器。

核心原则:小数据量/复杂Excel用 Apache POI,大数据量/普通读取用 EasyExcel;读取时务必处理空单元格和格式转换,避免空指针/格式错误。

相关推荐
胡萝卜3.06 小时前
构建安全的C++内存管理体系:从RAII到智能指针的完整解决方案
运维·开发语言·c++·人工智能·安全·智能指针·raii
MSTcheng.6 小时前
【C++】如何快速实现一棵支持key或key-value的二叉搜索树?关键技巧一文掌握!
开发语言·c++·算法·二叉搜索树
ByNotD0g7 小时前
Go 泛型 in 1.25
开发语言·后端·golang
野生风长7 小时前
从零开始的c语言:指针高级应用(下)(回调函数,qsort函数模拟实现, strlen和sizeof)
java·c语言·开发语言·c++·算法
g***B7387 小时前
Java 服务端架构的本质:从单体到云原生的演进与思维模式变革
java·云原生·架构
d111111111d7 小时前
嵌入式面试问题:STM32中指针和数组的本质区别是什么,常用数组存储什么数据?
java·笔记·stm32·单片机·嵌入式硬件·学习
chao1898447 小时前
C# 实现画板源码
开发语言·c#
yivifu7 小时前
Excel中Lookup函数实现临界点归入下一个等级的方法
java·前端·excel
大佐不会说日语~7 小时前
Spring AI Alibaba 对话记忆丢失问题:Redis 缓存过期后如何恢复 AI 上下文
java·人工智能·spring boot·redis·spring·缓存