杰瑞科技汇

Java POI如何实现Excel数据导入?

目录

  1. 环境准备
  2. 核心概念与 API 简介
  3. 基础教程:读取 .xlsx (Excel 2007+) 文件
    • 读取整个工作表内容
    • 读取指定单元格数据
  4. 处理不同类型的数据
  5. 高级教程:读取 .xls (Excel 97-2003) 文件
  6. 性能优化与最佳实践
  7. 完整代码示例
  8. 常见问题与解决方案

环境准备

你需要在你的 Java 项目中添加 Apache POI 的依赖,如果你使用 Maven,这是最简单的方式。

Java POI如何实现Excel数据导入?-图1
(图片来源网络,侵删)

Maven 依赖 (pom.xml)

你需要同时添加 poipoi-ooxml 两个库。

<dependencies>
    <!-- 核心库 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version> <!-- 建议使用较新版本 -->
    </dependency>
    <!-- 用于处理 .xlsx 格式的 OOXML 文件 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version>
    </dependency>
    <!-- 为了支持 XML 和其他 OO 格式,可能需要这个 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

如果你使用 Gradle,在 build.gradle 文件中添加:

implementation 'org.apache.poi:poi:5.2.5'
implementation 'org.apache.poi:poi-ooxml:5.2.5'
implementation 'org.apache.poi:poi-scratchpad:5.2.5'

核心概念与 API 简介

在开始编码前,了解 POI 的核心类非常重要。

类/接口 描述 备注
Workbook 代表整个 Excel 文档(工作簿)。 是一个顶层接口,有两个主要实现:HSSFWorkbookXSSFWorkbook
HSSFWorkbook 用于操作 .xls 格式的 Excel 文件(Excel 97-2003)。 由于其基于二进制格式,单个文件最大支持 65536 行、256 列。
XSSFWorkbook 用于操作 .xlsx 格式的 Excel 文件(Excel 2007+)。 基于 Office Open XML 格式,理论上支持更大的行数和列数。
Sheet 代表 Workbook 中的一个工作表(页签)。 一个 Workbook 可以包含多个 Sheet
Row 代表 Sheet 中的一行。 行号从 0 开始。
Cell 代表 Row 中的一个单元格。 列号从 0 开始。
DataFormatter 一个非常实用的工具类,用于获取单元格的显示值,而不管其底层类型是什么。 一个单元格是数字格式 "¥#,##0.00",它会返回 "¥1,234.50" 这样的字符串。

基础教程:读取 .xlsx (Excel 2007+) 文件

这是目前最主流的 Excel 格式,我们将使用 XSSFWorkbook

Java POI如何实现Excel数据导入?-图2
(图片来源网络,侵删)

假设你有一个 test.xlsx 文件,内容如下:

A B C
1 姓名 年龄 生日
2 张三 25 1998-05-10
3 李四 30 1993-01-20

示例代码:读取整个工作表内容

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelXlsx {
    public static void main(String[] args) {
        // 1. 定义文件路径
        String filePath = "path/to/your/test.xlsx";
        try ( // 2. 使用 try-with-resources 自动关闭流
              FileInputStream fis = new FileInputStream(filePath);
              // 3. 根据 .xlsx 文件创建 XSSFWorkbook 对象
              Workbook workbook = new XSSFWorkbook(fis)
        ) {
            // 4. 获取第一个工作表 (Sheet)
            Sheet sheet = workbook.getSheetAt(0); // 索引从 0 开始
            // 5. 遍历工作表中的每一行
            // sheet.iterator() 会跳过空行,如果需要处理所有行(包括空行),使用 sheet.rowIterator()
            for (Row row : sheet) {
                // 6. 遍历行中的每一个单元格
                for (Cell cell : row) {
                    // 7. 获取单元格的值
                    // 直接使用 cell.getStringCellValue() 会抛出异常,因为单元格类型可能不同
                    // 我们使用 switch-case 来处理不同类型的单元格
                    switch (cell.getCellType()) {
                        case STRING:
                            System.out.print(cell.getStringCellValue() + "\t");
                            break;
                        case NUMERIC:
                            // 判断是数字还是日期
                            if (DateUtil.isCellDateFormatted(cell)) {
                                System.out.print(cell.getDateCellValue() + "\t");
                            } else {
                                System.out.print(cell.getNumericCellValue() + "\t");
                            }
                            break;
                        case BOOLEAN:
                            System.out.print(cell.getBooleanCellValue() + "\t");
                            break;
                        case FORMULA:
                            // 获取公式计算后的值
                            System.out.print(cell.getCellFormula() + "\t");
                            break;
                        case BLANK:
                            System.out.print("[BLANK]\t");
                            break;
                        default:
                            System.out.print("[UNKNOWN]\t");
                    }
                }
                // 每行打印完后换行
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

更优雅的方式:使用 DataFormatter

上面的 switch-case 语句有点繁琐。DataFormatter 可以让代码更简洁。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.DataFormatter; // 引入 DataFormatter
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelWithFormatter {
    public static void main(String[] args) {
        String filePath = "path/to/your/test.xlsx";
        DataFormatter dataFormatter = new DataFormatter(); // 创建 DataFormatter 实例
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fis)) {
            Sheet sheet = workbook.getSheetAt(0);
            for (Row row : sheet) {
                for (Cell cell : row) {
                    // dataFormatter.formatCellValue(cell) 会智能地返回单元格的显示值
                    // 数字返回字符串形式的数字,日期返回字符串形式的日期
                    System.out.print(dataFormatter.formatCellValue(cell) + "\t");
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

注意DataFormatter 返回的都是 String 类型,如果你需要进行数学运算,仍然需要将字符串转换为相应的数字类型。


处理不同类型的数据

从上面的代码可以看出,正确处理单元格类型是关键。

Java POI如何实现Excel数据导入?-图3
(图片来源网络,侵删)
CellType 描述 获取方法
STRING 字符串 cell.getStringCellValue()
NUMERIC 数字或日期 cell.getNumericCellValue()
配合 DateUtil.isCellDateFormatted(cell) 判断是否为日期
BOOLEAN 布尔值 cell.getBooleanCellValue()
FORMULA 公式 cell.getCellFormula() (获取公式字符串)
需要 FormulaEvaluator 来计算结果
BLANK 空单元格 -
ERROR 错误 -

如何处理公式?

// ...
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
// ...
for (Cell cell : row) {
    if (cell.getCellType() == CellType.FORMULA) {
        // 计算公式的结果
        CellValue cellValue = evaluator.evaluate(cell);
        switch (cellValue.getCellType()) {
            case NUMERIC:
                System.out.print(cellValue.getNumberValue() + "\t");
                break;
            case STRING:
                System.out.print(cellValue.getStringValue() + "\t");
                break;
            // ... 其他类型
        }
    } else {
        // 非公式单元格,按原方式处理
        System.out.print(dataFormatter.formatCellValue(cell) + "\t");
    }
}
// ...

高级教程:读取 .xls (Excel 97-2003) 文件

读取 .xls 文件与 .xlsx 文件几乎完全相同,唯一的区别是创建 Workbook 的方式。

// ...
// 1. 文件路径
String filePath = "path/to/your/test.xls";
try (FileInputStream fis = new FileInputStream(filePath);
     // 2. 使用 HSSFWorkbook 而不是 XSSFWorkbook
     Workbook workbook = new HSSFWorkbook(fis)) {
    // 3. 后续代码与读取 .xlsx 完全一样
    Sheet sheet = workbook.getSheetAt(0);
    DataFormatter dataFormatter = new DataFormatter();
    // ... 遍历行和单元格
// ...

为了代码的通用性,你可以根据文件后缀名来决定使用哪个实现。

import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
public Workbook getWorkbook(String filePath) throws IOException {
    FileInputStream fis = new FileInputStream(filePath);
    if (filePath.endsWith(".xlsx")) {
        return new XSSFWorkbook(fis);
    } else if (filePath.endsWith(".xls")) {
        return new HSSFWorkbook(fis);
    }
    fis.close();
    throw new IllegalArgumentException("文件格式不支持,必须是 .xls 或 .xlsx");
}

性能优化与最佳实践

处理大型 Excel 文件时,内存消耗可能会很高。

  1. 使用 SXSSF (Streaming Usermodel) for .xlsx:

    • XSSFWorkbook 会将整个 Excel 文件加载到内存中,如果文件很大(比如几百兆),会导致 OutOfMemoryError
    • SXSSFWorkbookXSSFWorkbook 的流式版本,它只在内存中保留一定数量的行(默认 100),其余的行会临时写入磁盘,这极大地减少了内存使用。
    • 注意SXSSFWorkbook 是只写的,但在读取时,它也可以作为一种更节省内存的选择,因为它不会一次性加载所有数据。
    // SXSSFWorkbook 主要用于大数据量写入,但在读取时,如果内存是瓶颈,
    // 可以配合 XSSFEventBasedModel 或其他低内存 API。
    // 对于一般读取,XSSFWorkbook 如果文件不大,仍然是最方便的。
  2. 总是使用 try-with-resources:

    • Workbook, Sheet, Row, FileInputStream 等资源都需要被正确关闭。try-with-resources 语句可以确保这些资源在代码块执行完毕后自动关闭,避免内存泄漏。
  3. 避免不必要的对象创建:

    • 如果在循环中创建 DataFormatterFormulaEvaluator,可以将它们移到循环外部。
  4. 只读取需要的行:

    • 如果你知道数据从第 10 行开始,就不要从第 0 行开始遍历,可以使用 sheet.getFirstRowNum()sheet.getLastRowNum() 来获取数据范围。

完整代码示例

这是一个更完整的示例,它封装了读取逻辑,并处理了 .xls.xlsx 两种格式。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.DataFormatter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ExcelReader {
    /**
     * 读取 Excel 文件,并将所有行数据转换为 List<List<String>>
     * @param filePath Excel 文件路径
     * @return 包含所有行数据的列表
     */
    public List<List<String>> readExcel(String filePath) {
        List<List<String>> data = new ArrayList<>();
        DataFormatter dataFormatter = new DataFormatter();
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = getWorkbook(filePath, fis)) {
            Sheet sheet = workbook.getSheetAt(0); // 读取第一个工作表
            for (Row row : sheet) {
                List<String> rowData = new ArrayList<>();
                for (Cell cell : row) {
                    // 使用 DataFormatter 获取单元格的显示值
                    rowData.add(dataFormatter.formatCellValue(cell));
                }
                data.add(rowData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }
    /**
     * 根据 文件后缀名 创建相应的 Workbook 对象
     */
    private Workbook getWorkbook(String filePath, FileInputStream fis) throws IOException {
        if (filePath.endsWith(".xlsx")) {
            return new XSSFWorkbook(fis);
        } else if (filePath.endsWith(".xls")) {
            return new HSSFWorkbook(fis);
        }
        throw new IllegalArgumentException("不支持的文件类型: " + filePath);
    }
    public static void main(String[] args) {
        ExcelReader reader = new ExcelReader();
        String excelPath = "path/to/your/test.xlsx"; // 替换为你的文件路径
        List<List<String>> excelData = reader.readExcel(excelPath);
        // 打印读取到的数据
        for (List<String> row : excelData) {
            System.out.println(row);
        }
    }
}

常见问题与解决方案

Q1: java.lang.NoClassDefFoundError: org/openxmlformats/schemas/spreadsheetml/x2006/main/CTWorksheet

  • 原因: 你试图读取 .xlsx 文件,但只添加了 poi 依赖,缺少 poi-ooxml 依赖。
  • 解决: 确保 poi-ooxml 依赖已正确添加到你的项目中。

Q2: java.lang.OutOfMemoryError: Java heap space

  • 原因: Excel 文件过大,XSSFWorkbook 将整个文件加载到内存中,导致内存溢出。
  • 解决:
    1. 增加 JVM 堆内存大小(-Xmx1024m 等),这只是治标不治本。
    2. 使用 SXSSFWorkbook(主要用于写入)或研究 XSSFSAX (Event API) 结合的低内存读取方式,对于大多数业务场景,优化读取逻辑或分批处理数据更实际。

Q3: 读取的日期是数字(如 43586)

  • 原因: Excel 内部将日期存储为从 1900/1/1 开始的天数数字,你需要用 DataFormatter 或手动转换。
  • 解决:
    • 简单方法: 使用 DataFormatter,它会自动将日期格式化为字符串。
    • 手动转换:
      if (DateUtil.isCellDateFormatted(cell)) {
          Date date = cell.getDateCellValue();
          // 使用 SimpleDateFormat 将 Date 转换为想要的字符串格式
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
          System.out.print(sdf.format(date) + "\t");
      }

Q4: 读取的数字是科学计数法(如 1.23E+7)

  • 原因: Excel 的单元格格式被设置为科学计数法。
  • 解决:
    • 简单方法: 使用 DataFormatter,它会返回单元格显示的文本,即 "12300000"。
    • 手动转换: 你需要先获取原始数字值 cell.getNumericCellValue(),然后使用 DecimalFormat 等工具类进行格式化。

希望这份详细的指南能帮助你顺利地在 Java 项目中使用 POI 导入 Excel!

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