杰瑞科技汇

Java POI如何高效导入Excel数据?

目录

  1. 准备工作:引入 POI 依赖
  2. 核心概念:Workbook, Sheet, Row, Cell
  3. 完整代码示例:读取 .xlsx (Excel 2007+) 文件
  4. 处理不同版本的 Excel (.xls vs .xlsx)
  5. 读取单元格数据(处理不同类型)
  6. 读取复杂文件(合并单元格、公式、日期等)
  7. 最佳实践与性能优化
  8. 常见问题与解决方案 (FAQ)

准备工作:引入 POI 依赖

你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,在 pom.xml 文件中添加以下依赖:

Java POI如何高效导入Excel数据?-图1
(图片来源网络,侵删)
<dependencies>
    <!-- Apache POI Core -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.4</version> <!-- 建议使用较新版本 -->
    </dependency>
    <!-- 用于处理 .xlsx (OOXML) 格式 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.4</version>
    </dependency>
    <!-- 用于处理 .xls (BIFF) 格式 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.4</version>
    </dependency>
    <!-- 为了处理 XML 和一些依赖,通常需要这个 -->
    <dependency>
        <groupId>org.apache.xmlbeans</groupId>
        <artifactId>xmlbeans</artifactId>
        <version>5.1.1</version>
    </dependency>
    <!-- POI 依赖的日志库 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.7</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.7</version>
    </dependency>
</dependencies>

注意.xlsx 是目前的主流格式,对应的 POI 主类是 XSSFWorkbook.xls 是旧版格式,对应 HSSFWorkbook,代码中需要根据文件后缀选择不同的实现类。


核心概念:Workbook, Sheet, Row, Cell

POI 操作 Excel 的对象模型就像一个树状结构:

  • Workbook (工作簿):代表一个 Excel 文件,它是整个 Excel 文件的入口,你可以通过 Workbook 获取所有的 Sheet
  • Sheet (工作表):代表 Excel 文件中的一个工作表,"Sheet1",你可以通过 Sheet 获取所有的 Row
  • Row (行):代表工作表中的一行,你可以通过 Row 获取所有的 Cell
  • Cell (单元格):代表行中的一个单元格,是存储数据的最小单位,你可以从 Cell 中获取具体的数据值。

这个关系是:Workbook -> Sheet -> Row -> Cell


完整代码示例:读取 .xlsx (Excel 2007+) 文件

假设我们有一个名为 students.xlsx 的文件,内容如下:

Java POI如何高效导入Excel数据?-图2
(图片来源网络,侵删)
姓名 年龄 班级 入学日期
张三 20 一班 2025-09-01
李四 21 二班 2025-09-01
王五 19 一班 2025-09-01

下面是读取这个文件的完整 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 filePath = "path/to/your/students.xlsx"; // 请替换为你的文件路径
        // 使用 try-with-resources 语句自动关闭资源
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fis)) { // 根据文件类型选择 Workbook
            // 2. 获取第一个工作表 (Sheet)
            Sheet sheet = workbook.getSheetAt(0); // 索引从0开始
            // 3. 获取标题行 (第一行),用于获取列名
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                System.out.println("Excel文件为空或没有标题行!");
                return;
            }
            // 4. 遍历数据行 (从第二行开始)
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue; // 跳过空行
                }
                // 5. 获取单元格数据
                Cell nameCell = row.getCell(0);
                Cell ageCell = row.getCell(1);
                Cell classCell = row.getCell(2);
                Cell dateCell = row.getCell(3);
                // 6. 读取单元格值并处理
                String name = getCellValueAsString(nameCell);
                Integer age = getCellValueAsInteger(ageCell);
                String className = getCellValueAsString(classCell);
                Date admissionDate = getCellValueAsDate(dateCell);
                // 7. 打印或处理数据
                System.out.println("姓名: " + name + ", 年龄: " + age + ", 班级: " + className + ", 入学日期: " + admissionDate);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 将单元格内容转换为字符串
     */
    private static String getCellValueAsString(Cell cell) {
        if (cell == null) {
            return "";
        }
        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue();
            case NUMERIC:
                // 处理数字可能是日期的情况
                if (DateUtil.isCellDateFormatted(cell)) {
                    return new SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue());
                } else {
                    // 普通数字,可以格式化输出,如保留整数
                    return String.valueOf((int) cell.getNumericCellValue());
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                // 如果是公式,可以获取公式的计算结果
                return String.valueOf(cell.getNumericCellValue());
            case BLANK:
                return "";
            default:
                return "";
        }
    }
    /**
     * 将单元格内容转换为整数
     */
    private static Integer getCellValueAsInteger(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case NUMERIC:
                return (int) cell.getNumericCellValue();
            case STRING:
                try {
                    return Integer.parseInt(cell.getStringCellValue().trim());
                } catch (NumberFormatException e) {
                    return null;
                }
            default:
                return null;
        }
    }
    /**
     * 将单元格内容转换为日期
     */
    private static Date getCellValueAsDate(Cell cell) {
        if (cell == null) {
            return null;
        }
        switch (cell.getCellType()) {
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    return cell.getDateCellValue();
                } else {
                    // 如果数字被存储为日期,但未格式化,需要转换
                    return DateUtil.getJavaDate(cell.getNumericCellValue());
                }
            case STRING:
                try {
                    // 尝试解析字符串格式的日期
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                    return sdf.parse(cell.getStringCellValue());
                } catch (Exception e) {
                    return null;
                }
            default:
                return null;
        }
    }
}

处理不同版本的 Excel (.xls vs .xlsx)

代码中 Workbook 的创建方式决定了它处理的 Excel 版本。

  • .xlsx (Office 2007+): 使用 XSSFWorkbook
    Workbook workbook = new XSSFWorkbook(new FileInputStream("file.xlsx"));
  • .xls (Office 97-2003): 使用 HSSFWorkbook
    Workbook workbook = new HSSFWorkbook(new FileInputStream("file.xls"));

你可以通过文件后缀名来判断使用哪个类,但更通用的方法是直接用 WorkbookFactory,它会自动识别文件类型。

// 更通用的方式,自动识别 .xls 或 .xlsx
try (FileInputStream fis = new FileInputStream("file.xlsx");
     Workbook workbook = WorkbookFactory.create(fis)) {
    // ... 后续代码完全相同
}

WorkbookFactory 是 POI 推荐的方式,因为它更灵活。


读取单元格数据(处理不同类型)

单元格的数据类型是多样的,Cell 对象提供了 getCellType() 方法来获取类型,常见的类型有:

  • CellType.STRING: 字符串
  • CellType.NUMERIC: 数字(包括日期、时间)
  • CellType.BOOLEAN: 布尔值
  • CellType.FORMULA: 公式
  • CellType.BLANK: 空单元格
  • CellType.ERROR: 错误

如何区分数字和日期? 对于 CellType.NUMERIC 类型的单元格,你需要使用 DateUtil.isCellDateFormatted(cell) 来判断它是否是一个被格式化为日期的单元格,如果是,则用 cell.getDateCellValue() 获取;否则,用 cell.getNumericCellValue() 获取。

Cell cell = row.getCell(0);
if (cell.getCellType() == CellType.NUMERIC) {
    if (DateUtil.isCellDateFormatted(cell)) {
        Date date = cell.getDateCellValue();
        System.out.println("日期: " + date);
    } else {
        double number = cell.getNumericCellValue();
        System.out.println("数字: " + number);
    }
}

读取复杂文件(合并单元格、公式、日期等)

合并单元格

合并单元格的值只存在于左上角的那个单元格中,你需要先检查某个单元格是否是合并单元格,如果是,则获取其合并后的值。

// 假设在循环中
Cell cell = row.getCell(0);
// 检查单元格是否在合并单元格区域内
for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
    CellRangeAddress mergedRegion = sheet.getMergedRegion(i);
    if (mergedRegion.isInRange(cell.getRowIndex(), cell.getColumnIndex())) {
        // 如果是,则获取合并区域左上角的单元格的值
        Row firstRow = sheet.getRow(mergedRegion.getFirstRow());
        Cell firstCell = firstRow.getCell(mergedRegion.getFirstColumn());
        String mergedValue = firstCell.getStringCellValue();
        System.out.println("合并单元格的值: " + mergedValue);
        break;
    }
}

公式

对于包含公式的单元格,默认情况下 cell.getStringCellValue()cell.getNumericCellValue() 返回的是公式的计算结果,如果你想获取公式本身,可以使用 cell.getCellFormula()

Cell formulaCell = row.getCell(4);
if (formulaCell.getCellType() == CellType.FORMULA) {
    String formula = formulaCell.getCellFormula();
    System.out.println("公式: " + formula);
    // 获取计算结果
    double result = formulaCell.getNumericCellValue();
    System.out.println("计算结果: " + result);
}

最佳实践与性能优化

  1. 使用 try-with-resources:务必使用 try-with-resources 语句来包裹 FileInputStreamWorkbook 对象,确保它们在使用完毕后能被自动关闭,避免资源泄漏。

    try (FileInputStream fis = new FileInputStream(...);
         Workbook workbook = WorkbookFactory.create(fis)) {
        // ...
    } catch (IOException e) {
        // ...
    }
  2. 只读取需要的 Sheet:如果文件很大,但只需要第一个工作表,使用 workbook.getSheet("Sheet1")workbook.getSheetAt(0),避免加载所有工作表的数据。

  3. 避免创建不必要的对象:在循环中,尽量重用对象,减少 new 操作。

  4. 考虑 SXSSF (流式 API):对于超大的 Excel 文件(例如几十万行),XSSFWorkbook 会将整个文件加载到内存中,可能导致 OutOfMemoryError,这时,应该使用 SXSSFWorkbook (Streaming Usermodel API),它会把数据写入临时文件,只保留一部分数据在内存中,从而大大减少内存占用。

    // 创建一个只保留100行在内存中的SXSSFWorkbook
    Workbook workbook = new SXSSFWorkbook(100);
    // ... 写入数据
    // 最后需要调用write方法将数据写入输出流,并清理临时文件
    ( (SXSSFWorkbook) workbook ).dispose();

常见问题与解决方案 (FAQ)

Q1: java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook A: 这是因为你的依赖不完整,确保你已经添加了 poipoi-ooxml 这两个核心依赖。

Q2: 如何处理单元格为空的情况? A: 在读取 Cell 之前,先判断 row.getCell(columnIndex) 是否返回 null,如果返回 null,说明该单元格不存在(可能是空行或空列)。cell 对象不为 null,再判断 cell.getCellType() == CellType.BLANK

Q3: 为什么读取的数字是科学计数法(如 23E+7)? A: 因为 cell.getNumericCellValue() 返回的是 double 类型,当数字很大或很小时,Java 会自动使用科学计数法显示,你可以使用 DecimalFormat 来格式化输出。

    double number = cell.getNumericCellValue();
    DecimalFormat df = new DecimalFormat("#");
    System.out.println(df.format(number));

Q4: 为什么读取的日期是数字(如 44197)? A: 因为 Excel 内部将日期存储为一个从 1900-01-01 开始的天数序列,你需要使用 DateUtil 工具类来转换它。

    if (DateUtil.isCellDateFormatted(cell)) {
        Date date = cell.getDateCellValue();
    } else {
        // 如果没有格式化,但知道它是日期,可以这样转换
        Date date = DateUtil.getJavaDate(cell.getNumericCellValue());
    }

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

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