杰瑞科技汇

Java POI如何高效读取Excel数据?

本教程将涵盖从最基础的 .xls (Excel 97-2003) 到现代的 .xlsx (Excel 2007+) 格式,并提供从简单到复杂的完整代码示例。

Java POI如何高效读取Excel数据?-图1
(图片来源网络,侵删)

目录

  1. 准备工作:添加 POI 依赖
  2. 核心概念:Workbook, Sheet, Row, Cell
  3. 读取 .xls 文件 (HSSFWorkbook)
  4. 读取 .xlsx 文件 (XSSFWorkbook)
  5. 统一 API 读取任意格式 (XSSF/HSSF)
  6. 进阶:处理不同数据类型的单元格
  7. 进阶:读取大文件(SAX 模式,避免内存溢出)
  8. 完整示例代码
  9. 总结与最佳实践

准备工作:添加 POI 依赖

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

<!-- POI 核心依赖 -->
<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-lite</artifactId>
    <version>5.2.5</version>
</dependency>

如果你使用 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-ooxml-lite:5.2.5'

核心概念

理解 POI 的对象模型是关键,它就像一个树形结构:

  • Workbook (工作簿):代表整个 Excel 文件,对于 .xls,它是 HSSFWorkbook;对于 .xlsx,它是 XSSFWorkbook
  • Sheet (工作表):代表 Excel 文件中的一个工作表(Sheet1, Sheet2)。
  • Row (行):代表工作表中的一行,行号从 0 开始。
  • Cell (单元格):代表行中的一个单元格,列号也从 0 开始。

读取流程就是:Workbook -> Sheet -> Row -> Cell

Java POI如何高效读取Excel数据?-图2
(图片来源网络,侵删)

读取 .xls 文件 (HSSFWorkbook)

.xls 是旧版的 Excel 格式,使用 HSSFWorkbook 类处理。

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadXlsExample {
    public static void main(String[] args) {
        // 1. 定义文件路径
        String filePath = "path/to/your/data.xls";
        // 使用 try-with-resources 语句,确保文件流被自动关闭
        try (FileInputStream fis = new FileInputStream(filePath);
             // 2. 创建 HSSFWorkbook 对象,整个 Excel 文件加载到内存
             Workbook workbook = new HSSFWorkbook(fis)) {
            // 3. 获取第一个工作表 (Sheet)
            Sheet sheet = workbook.getSheetAt(0); // 0 表示第一个工作表
            // 4. 遍历工作表中的每一行
            for (Row row : sheet) {
                // 5. 遍历行中的每一个单元格
                for (Cell cell : row) {
                    // 6. 获取单元格的值并打印
                    // 注意:直接使用 cell.getStringCellValue() 可能会抛出异常,如果单元格类型不是字符串
                    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;
                        default:
                            System.out.print("UNKNOWN\t");
                    }
                }
                System.out.println(); // 换行
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

读取 .xlsx 文件 (XSSFWorkbook)

.xlsx 是现代的 Excel 格式,使用 XSSFWorkbook 类处理,代码结构与 .xls 非常相似。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadXlsxExample {
    public static void main(String[] args) {
        String filePath = "path/to/your/data.xlsx";
        try (FileInputStream fis = new FileInputStream(filePath);
             // 注意:这里使用 XSSFWorkbook
             Workbook workbook = new XSSFWorkbook(fis)) {
            Sheet sheet = workbook.getSheetAt(0);
            for (Row row : sheet) {
                for (Cell cell : row) {
                    // 获取单元格值的逻辑与 .xls 完全相同
                    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;
                        default:
                            System.out.print("UNKNOWN\t");
                    }
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

统一 API 读取任意格式 (XSSF/HSSF)

为了代码的通用性,我们最好写一个方法,能自动识别文件格式并使用相应的 API 读取,POI 提供了 WorkbookFactory 工厂类来实现这一点。

这是最推荐的方式,因为它让你的代码更加健壮和灵活。

Java POI如何高效读取Excel数据?-图3
(图片来源网络,侵删)
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelUnifiedExample {
    public static void main(String[] args) {
        String filePath = "path/to/your/data.xlsx"; // 也可以是 .xls
        readExcel(filePath);
    }
    public static void readExcel(String filePath) {
        try (FileInputStream fis = new FileInputStream(filePath);
             // 使用 WorkbookFactory 自动创建 HSSFWorkbook 或 XSSFWorkbook
             Workbook workbook = WorkbookFactory.create(fis)) {
            System.out.println("工作表数量: " + workbook.getNumberOfSheets());
            Sheet sheet = workbook.getSheetAt(0);
            // 使用迭代器遍历,更安全
            for (Row row : sheet) {
                for (Cell cell : row) {
                    // 从 POI 3.15 开始,可以使用 getCellType() 方法,它返回枚举类型
                    CellType cellType = cell.getCellType();
                    if (cellType == CellType.STRING) {
                        System.out.print(cell.getStringCellValue() + "\t");
                    } else if (cellType == CellType.NUMERIC) {
                        if (DateUtil.isCellDateFormatted(cell)) {
                            System.out.print(cell.getDateCellValue() + "\t");
                        } else {
                            System.out.print(cell.getNumericCellValue() + "\t");
                        }
                    } else if (cellType == CellType.BOOLEAN) {
                        System.out.print(cell.getBooleanCellValue() + "\t");
                    } else if (cellType == CellType.FORMULA) {
                        System.out.print(cell.getCellFormula() + "\t");
                    }
                    // 其他类型如 BLANK, ERROR 等
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

进阶:处理不同数据类型的单元格

在读取单元格时,直接调用 getStringCellValue() 等方法可能会因为类型不匹配而抛出 IllegalStateException始终使用 getCellType() 进行判断 是最佳实践。

POI 5.x 版本引入了新的 CellType 枚举,推荐使用它来替代旧的常量。

枚举值 (CellType) 说明 获取值的方法
STRING 字符串 cell.getStringCellValue()
NUMERIC 数字/日期 cell.getNumericCellValue() / cell.getDateCellValue()
BOOLEAN 布尔值 cell.getBooleanCellValue()
FORMULA 公式 cell.getCellFormula() / cell.getCachedFormulaResultType()
BLANK 空单元格 -
ERROR 错误 -

处理数字格式化(显示的值 vs. 实际值) 有时,单元格中存的是数字,但被格式化为显示为百分比或货币。getNumericCellValue() 返回的是实际的数字值,如果你想获取格式化后的字符串,可以使用 DataFormatter

import org.apache.poi.ss.usermodel.DataFormatter;
// ...
DataFormatter dataFormatter = new DataFormatter();
for (Row row : sheet) {
    for (Cell cell : row) {
        // dataFormatter.formatCellValue() 会返回单元格显示的值
        // 如果是数字,它会返回格式化后的字符串(如 "50%")
        // 如果是字符串,它会返回字符串本身
        // 如果是公式,它会返回公式的计算结果
        System.out.print(dataFormatter.formatCellValue(cell) + "\t");
    }
    System.out.println();
}
// ...

进阶:读取大文件(SAX 模式,避免内存溢出)

HSSFWorkbookXSSFWorkbook 都会将整个 Excel 文件加载到内存中,如果文件非常大(例如几百 MB),很容易导致 OutOfMemoryError

对于 .xlsx 文件,POI 提供了 SAX (Event API) 模式,它以流的方式读取文件,逐行解析,内存占用非常小。

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.File;
import java.io.InputStream;
public class ReadLargeXlsxExample {
    public static void main(String[] args) throws Exception {
        String filePath = "path/to/your/large_data.xlsx";
        processOneSheet(filePath, 0); // 读取第一个工作表
    }
    public static void processOneSheet(String filename, int sheetId) throws Exception {
        File xlsxFile = new File(filename);
        // 1. 创建 XSSFReader
        XSSFReader xssfReader = new XSSFReader(xssfFile);
        // 2. 获取共享字符串表
        SharedStringsTable sst = xssfReader.getSharedStringsTable();
        // 3. 获取 XMLReader
        XMLReader parser = XMLReaderFactory.createXMLReader();
        // 4. 设置内容处理器
        ContentHandler handler = new SheetHandler(sst);
        parser.setContentHandler(handler);
        // 5. 获取指定工作表的 InputStream
        InputStream sheet = xssfReader.getSheet("rId" + (sheetId + 1)); // rId1 是第一个 sheet
        InputSource sheetSource = new InputSource(sheet);
        // 6. 开始解析
        parser.parse(sheetSource);
    }
    // 自定义的 SheetHandler,用于处理 XML 事件
    // 这是一个简化的示例,实际实现会更复杂
    private static class SheetHandler implements ContentHandler {
        private SharedStringsTable sst;
        private String lastContents;
        private boolean nextIsString;
        public SheetHandler(SharedStringsTable sst) {
            this.sst = sst;
        }
        @Override
        public void startElement(String uri, String localName, String qName, org.xml.sax.Attributes attributes) {
            if (qName.equals("c")) { // <c> 单元格元素
                String cellType = attributes.getValue("t");
                if (cellType != null && cellType.equals("s")) {
                    nextIsString = true; // 如果类型是 "s",说明内容是共享字符串表的索引
                } else {
                    nextIsString = false;
                }
            }
            // 清空 lastContents
            lastContents = "";
        }
        @Override
        public void characters(char[] ch, int start, int length) {
            lastContents += new String(ch, start, length);
        }
        @Override
        public void endElement(String uri, String localName, String qName) {
            if (qName.equals("v")) { // <v> 是单元格的值
                if (nextIsString) {
                    int idx = Integer.parseInt(lastContents);
                    System.out.print(sst.getItemAt(idx).getString() + "\t");
                } else {
                    System.out.print(lastContents + "\t");
                }
            } else if (qName.equals("row")) { // 一行结束
                System.out.println();
            }
        }
    }
}

注意:SAX 模式比用户模式复杂得多,因为它直接操作底层的 XML,你需要自己处理 XML 事件来构建行和单元格,如果你的文件不是特别大(小于 100MB),建议还是使用 WorkbookFactory 的用户模式,因为它简单得多。


完整示例代码

这是一个结合了上述所有优点的完整示例:使用 WorkbookFactoryDataFormatterCellType 枚举。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class CompletePoiReadExample {
    public static void main(String[] args) {
        // 请替换为你的 Excel 文件路径
        String filePath = "C:\\data\\employees.xlsx";
        File excelFile = new File(filePath);
        if (!excelFile.exists()) {
            System.out.println("文件不存在: " + filePath);
            return;
        }
        try (FileInputStream fis = new FileInputStream(excelFile);
             Workbook workbook = WorkbookFactory.create(fis)) {
            System.out.println("成功加载工作簿: " + workbook.getSheetName(0));
            // 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 创建 DataFormatter,用于获取单元格的显示值
            DataFormatter dataFormatter = new DataFormatter();
            // 遍历每一行
            for (Row row : sheet) {
                // 遍历每一个单元格
                for (Cell cell : row) {
                    // 打印单元格类型和值
                    System.out.print("[" + row.getRowNum() + "," + cell.getColumnIndex() + "] ");
                    System.out.print("类型: " + cell.getCellType() + ", 值: ");
                    // 使用 DataFormatter 获取格式化后的值
                    System.out.print(dataFormatter.formatCellValue(cell) + "\t");
                }
                System.out.println(); // 每行结束后换行
            }
        } catch (IOException | InvalidFormatException e) {
            System.err.println("读取 Excel 文件时出错!");
            e.printStackTrace();
        }
    }
}

总结与最佳实践

  1. 首选 WorkbookFactory:使用 WorkbookFactory.create() 来创建 Workbook 对象,它能自动处理 .xls.xlsx 格式,让你的代码更具通用性。
  2. 使用 try-with-resources:确保所有 InputStreamWorkbook 对象在使用后都能被正确关闭,防止资源泄漏。
  3. 始终检查单元格类型:在读取单元格值之前,使用 cell.getCellType() 进行判断,避免因类型不匹配而抛出异常。
  4. 利用 DataFormatter:当你需要获取单元格格式化后的显示值(特别是数字和日期)时,DataFormatter 是最简单、最可靠的选择。
  5. 注意大文件处理:对于非常大的 .xlsx 文件,考虑使用 SAX 模式(XSSFReader)来避免内存溢出,但对于大多数常规大小的文件,用户模式已经足够。
  6. 处理异常:妥善处理 IOExceptionInvalidFormatException,确保程序的健壮性。

希望这份详细的教程能帮助你掌握 Java POI 读取 Excel 的技能!

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