杰瑞科技汇

Java POI如何高效读取Excel文件?

下面我将为你提供一个详细的、从入门到实践的指南,包括:

Java POI如何高效读取Excel文件?-图1
(图片来源网络,侵删)
  1. 环境准备:如何添加 POI 依赖。
  2. 核心概念:了解 XSSFWorkbookHSSFWorkbook 的区别。
  3. 基础读取:读取 .xlsx.xls 文件的基本步骤。
  4. 完整代码示例:包含处理不同数据类型和异常处理的完整可运行代码。
  5. 进阶技巧:读取公式、日期、样式等。

环境准备 (Maven)

如果你使用 Maven,在 pom.xml 文件中添加 POI 的依赖。强烈推荐使用 poi-ooxml,因为它同时支持旧版 .xls 和新版 .xlsx 格式。

<dependencies>
    <!-- Apache POI 核心库 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version> <!-- 建议使用最新稳定版 -->
    </dependency>
    <!-- 用于处理 Office Open XML 格式(.xlsx)的库 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version> <!-- 建议使用最新稳定版 -->
    </dependency>
    <!-- 如果需要处理 .xlsx 中的图表等,可能需要这个 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml-lite</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

核心概念:XSSFWorkbook vs HSSFWorkbook

在开始编码前,必须了解 POI 处理两种不同 Excel 格式的核心类:

特性 HSSFWorkbook XSSFWorkbook
支持的文件格式 .xls (Excel 97-2003) .xlsx (Excel 2007 及以后)
底层技术 基于 BIFF (Binary Interchange File Format) 基于 OOXML (Office Open XML),本质是 ZIP 包
内存消耗 ,所有数据都会加载到内存中。 较低,支持 SAX 模式(XSSFSAX)进行事件驱动解析,内存占用小,适合大文件。
POI 模块 poi poi-ooxml

  • 如果你的文件是 .xls,使用 HSSFWorkbook
  • 如果你的文件是 .xlsx,使用 XSSFWorkbook
  • 为了代码的通用性和健壮性,你可以通过文件后缀名来决定使用哪个类。

基础读取步骤

读取 Excel 文件的基本流程可以概括为以下四步:

Java POI如何高效读取Excel文件?-图2
(图片来源网络,侵删)
  1. 加载文件:使用 FileInputStream 读取 Excel 文件到输入流。
  2. 创建工作簿对象:根据文件类型,创建 XSSFWorkbookHSSFWorkbook 对象。
  3. 获取工作表:通过工作簿对象,按名称或索引获取 Sheet 对象。
  4. 遍历行和单元格:通过 Sheet 对象获取 Row 对象,再通过 Row 对象获取 Cell 对象,最后读取单元格的值。

完整代码示例

下面是一个完整的 Java 类,它可以读取一个 .xlsx 文件,并打印出所有内容,代码中包含了详细的注释和异常处理。

示例 Excel 文件 (data.xlsx)

姓名 年龄 入职日期 薪资 是否在职
张三 28 2025-05-20 50 true
李四 35 2025-11-01 00 false
王五 22 2025-03-15 00 true

ExcelReader.java

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ExcelReader {
    public static void main(String[] args) {
        // 1. 定义要读取的文件路径
        String excelFilePath = "path/to/your/data.xlsx"; // <--- 请替换为你的文件路径
        try ( // 使用 try-with-resources 确保 FileInputStream 和 Workbook 自动关闭
              FileInputStream fis = new FileInputStream(excelFilePath);
              // 2. 根据文件类型创建 Workbook 对象
              // .xlsx 文件使用 XSSFWorkbook
              Workbook workbook = new XSSFWorkbook(fis)
        ) {
            // 3. 获取第一个工作表 (Sheet)
            // 也可以通过名称获取:Sheet sheet = workbook.getSheet("员工信息");
            Sheet sheet = workbook.getSheetAt(0);
            // 定义一个日期格式化器,用于处理日期单元格
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            System.out.println("开始读取 Excel 文件内容...");
            // 4. 遍历工作表中的每一行
            // 从第一行开始,跳过表头 (sheet.getFirstRowNum() 是 0)
            for (int rowNum = 0; rowNum <= sheet.getLastRowNum(); rowNum++) {
                Row row = sheet.getRow(rowNum);
                // 如果行是空的,则跳过
                if (row == null) {
                    continue;
                }
                // 遍历行中的每一个单元格
                for (int cellNum = 0; cellNum < row.getLastCellNum(); cellNum++) {
                    Cell cell = row.getCell(cellNum);
                    // 如果单元格是空的,则跳过
                    if (cell == null) {
                        System.out.print("\t"); // 保持对齐
                        continue;
                    }
                    // 5. 根据单元格类型获取值
                    String cellValue = getCellValueAsString(cell, dateFormat);
                    System.out.print(cellValue + "\t"); // 用制表符隔开
                }
                System.out.println(); // 每行结束后换行
            }
            System.out.println("Excel 文件读取完成!");
        } catch (IOException e) {
            System.err.println("读取 Excel 文件时发生错误: " + e.getMessage());
            e.printStackTrace();
        }
    }
    /**
     * 将 Cell 对象的值转换为 String
     * @param cell 单元格对象
     * @param dateFormat 日期格式化器
     * @return 单元格值的字符串表示
     */
    private static String getCellValueAsString(Cell cell, SimpleDateFormat dateFormat) {
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                // 处理日期
                if (DateUtil.isCellDateFormatted(cell)) {
                    Date date = cell.getDateCellValue();
                    return dateFormat.format(date);
                } else {
                    // 处理普通数字
                    // 为了防止数字过长被科学计数法,或者变成.0,可以这样处理
                    double numericValue = cell.getNumericCellValue();
                    if (numericValue == (long) numericValue) {
                        return String.valueOf((long) numericValue);
                    } else {
                        return String.valueOf(numericValue);
                    }
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                // 如果单元格是公式,可以获取公式的计算结果
                // 注意:需要先 workbook.getCreationHelper().createFormulaEvaluator().evaluateFormulaCell(cell)
                // 然后根据返回的 CellValue 类型来取值
                // 这里简单处理,直接获取公式的字符串
                return cell.getCellFormula();
            case BLANK:
                return "";
            default:
                return "";
        }
    }
}

进阶技巧

1 读取公式

要读取单元格中公式的计算结果,而不是公式本身,你需要使用 FormulaEvaluator

// 在创建 Workbook 后,获取 FormulaEvaluator
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
// 在遍历单元格时
Cell cell = row.getCell(cellNum);
if (cell.getCellType() == CellType.FORMULA) {
    // 评估公式,获取结果
    CellValue evaluatedValue = evaluator.evaluate(cell);
    switch (evaluatedValue.getCellType()) {
        case STRING:
            System.out.print(evaluatedValue.getStringValue() + "\t");
            break;
        case NUMERIC:
            System.out.print(evaluatedValue.getNumberValue() + "\t");
            break;
        // ... 其他类型
    }
}

2 读取日期

如上面的完整代码所示,使用 DateUtil.isCellDateFormatted(cell) 来判断一个数字单元格是否是日期格式,如果是,则用 cell.getDateCellValue() 获取 Date 对象,再用 SimpleDateFormat 进行格式化。

3 读取样式(字体、颜色、背景等)

读取样式信息稍微复杂一些,因为它涉及到多个对象。

Java POI如何高效读取Excel文件?-图3
(图片来源网络,侵删)
// 假设 cell 已经存在
CellStyle cellStyle = cell.getCellStyle();
// 1. 获取字体
Font font = workbook.getFontAt(cellStyle.getFontIndex());
System.out.println("字体名称: " + font.getFontName());
System.out.println("字体大小: " + font.getFontHeightInPoints());
System.out.println("是否加粗: " + font.getBold());
// 2. 获取背景颜色
// 注意:颜色索引需要从工作簿的调色板中获取
short bgColorIndex = cellStyle.getFillForegroundColorIndex();
XSSFColor xssfColor = (XSSFColor) workbook.getCellStyleAt(cellStyle.getIndex()).getFillForegroundColorColor();
if (xssfColor != null) {
    byte[] rgb = xssfColor.getRGB();
    System.out.println("背景颜色 RGB: " + String.format("%02X%02X%02X", rgb[0], rgb[1], rgb[2]));
}
// 3. 获取对齐方式
System.out.println("水平对齐: " + cellStyle.getAlignment());
System.out.println("垂直对齐: " + cellStyle.getVerticalAlignment());

4 处理大文件(SAX 模式)

对于非常大的 .xlsx 文件(几百MB甚至上GB),使用 XSSFWorkbook 会耗尽所有内存,这时必须使用 SAX 模式(事件驱动模型)

它不会一次性将整个文件加载到内存,而是像 XML 解析一样,逐行读取事件(如:开始行、结束行、单元格数据等)。

你需要使用 XSSFSAX 的组合类,org.apache.poi.xssf.eventusermodel.XSSFReaderorg.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator

这个模型比 DOM 模型(XSSFWorkbook)复杂,但内存效率极高,具体实现可以参考 POI 官方文档或相关教程。

任务 关键类/方法 备注
加载文件 new FileInputStream(path) 使用 try-with-resources
创建工作簿 new XSSFWorkbook(fis) (.xlsx) 或 new HSSFWorkbook(fis) (.xls) 推荐使用 poi-ooxml
获取工作表 workbook.getSheetAt(0)workbook.getSheet("name") 索引从0开始
获取行 sheet.getRow(i) i 是行号
获取单元格 row.getCell(j) j 是列号
获取单元格值 cell.getStringCellValue(), cell.getNumericCellValue() 必须先用 getCellType() 判断类型
处理日期 DateUtil.isCellDateFormatted(cell) + cell.getDateCellValue()
处理公式 workbook.getCreationHelper().createFormulaEvaluator().evaluate(cell)
处理大文件 XSSFReader + SAX 事件驱动 内存效率高,代码复杂

希望这份详细的指南能帮助你顺利地使用 Java POI 读取 Excel 文件!

分享:
扫描分享到社交APP
上一篇
下一篇