首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Java Apache POI 讀取 Excel 完整教程:HSSF/XSSF 雙格式支援與大數據流式處理

     編輯:PHP 相關知識

前言

Apache POI 是 Java 操作 Microsoft Office 文件的標準庫。讀取 Excel 分為 HSSF(.xls,BIFF8 格式)與 XSSF(.xlsx,OOXML 格式)兩套 API,另提供 SXSSF(流式寫入)與 XSSFEventFactory(流式讀取)解決大檔案記憶體問題。本文從基礎讀取到百萬行高效能處理全面解析。

一、Maven 依賴

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.3.0</version>
</dependency>
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.3.0</version>
</dependency>
<!-- 大檔案流式處理可選 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml-schemas</artifactId>
    <version>5.3.0</version>
</dependency>

二、統一入口:WorkbookFactory 自動識別格式

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellReference;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.List;

public class ExcelReader {

    /** 讀取 Excel 回傳 List<List<String>>(每行一個 List) */
    public static List> read(String filePath) throws Exception {
        List> result = new ArrayList<>();
        
        try (FileInputStream fis = new FileInputStream(new File(filePath));
             Workbook wb = WorkbookFactory.create(fis)) {  // 自動判斷 .xls/.xlsx
            
            Sheet sheet = wb.getSheetAt(0); // 讀取第一張工作表
            int lastRowNum = sheet.getLastRowNum();
            
            for (int i = 0; i <= lastRowNum; i++) {
                Row row = sheet.getRow(i);
                if (row == null) continue; // 空行跳過
                
                List rowData = new ArrayList<>();
                int lastCellNum = row.getLastCellNum();
                
                for (int j = 0; j < lastCellNum; j++) {
                    Cell cell = row.getCell(j);
                    rowData.add(getCellValueAsString(cell));
                }
                result.add(rowData);
            }
        }
        return result;
    }

    /** 將任意型別儲存格轉為字串 */
    private static String getCellValueAsString(Cell cell) {
        if (cell == null) return "";
        
        return switch (cell.getCellType()) {
            case STRING -> cell.getStringCellValue().trim();
            case NUMERIC -> {
                if (DateUtil.isCellDateFormatted(cell)) {
                    yield cell.getLocalDateTimeCellValue().toString(); // 日期格式
                }
                // 數值去除科學記號、尾隨 .0
                yield new DataFormatter().formatCellValue(cell);
            }
            case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
            case FORMULA -> {
                // 公式儲存格:嘗試取快取值,否則回傳公式字串
                try {
                    yield getCellValueAsString(cell.getCachedFormulaResultType() != CellType.FORMULA 
                        ? cell : null); // 遞歸處理快取結果
                } catch (Exception e) {
                    yield cell.getCellFormula();
                }
            }
            case BLANK, _NONE, ERROR -> "";
            default -> "";
        };
    }
}

三、大檔案流式讀取(SXSSF / XSSFEventFactory)

當 Excel 超過 10 萬行,DOM 模式會導致 OOM。使用 SAX 事件驅動模式逐行處理,記憶體佔用恆定:

import org.apache.poi.xssf.eventusermodel.*;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;

public class StreamingExcelReader extends DefaultHandler {
    private SharedStringsTable sst;
    private String lastContents;
    private boolean nextIsString;
    private int rowCount = 0;

    public void process(String filePath) throws Exception {
        try (OPCPackage pkg = OPCPackage.open(filePath)) {
            XSSFReader reader = new XSSFReader(pkg);
            sst = reader.getSharedStringsTable();
            
            XMLReader parser = XMLReaderFactory.createXMLReader();
            parser.setContentHandler(this);
            parser.parse(new InputSource(reader.getSheetsData().next()));
        }
    }

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attrs) {
        if ("c".equals(qName)) { // 儲存格
            String cellType = attrs.getValue("t");
            nextIsString = "s".equals(cellType); // 共享字串表
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) {
        if ("v".equals(qName)) { // 值
            String value = nextIsString ? sst.getItemAt(Integer.parseInt(lastContents)).toString() : lastContents;
            System.out.print(value + "\t");
            nextIsString = false;
        } else if ("row".equals(qName)) {
            System.out.println();
            rowCount++;
        }
    }

    @Override
    public void characters(char[] ch, int start, int length) {
        lastContents = new String(ch, start, length);
    }
}

四、常見坑點與解決方案

問題原因解決
數值讀成科學記號 (1.23E7)預設格式化用 DataFormatter 或 BigDecimal 處理
日期讀成數字 (44927)Excel 日期存為序數DateUtil.isCellDateFormatted(cell) 判斷後轉換
公式儲存格回傳 null未計算公式用 FormulaEvaluator 強制計算,或讀取快取值
大檔案 OOMDOM 全載入記憶體改用 SAX 流式讀取(上文範例)
.xls 格式不支援超過 65536 行BIFF8 格式限制轉換為 .xlsx 或分多檔處理

五、效能優化建議

  1. 關閉自動計算公式:wb.setForceFormulaRecalculation(false)
  2. 複用 CellStyle:避免大量建立樣式物件導致 styles.xml 膨脹
  3. 批次提交資料庫:讀取 1000 行 → 批次 INSERT → 清理 List
  4. 使用 SXSSF 寫入大檔案:new SXSSFWorkbook(100) 保留 100 行在記憶體,其餘刷入磁碟

六、替代方案比較

庫優點缺點適用場景
Apache POI功能最全、社群活躍、支援所有格式記憶體佔用大、API 複雜企業級專案、複雜報表
EasyExcel (Alibaba)記憶體極低、API 簡單、註解驅動不支援 .xls、樣式功能弱大量資料匯入匯出、簡單報表
jxl (JExcelApi)輕量、支援 .xls已停止維護、不支援 .xlsx舊專案維護
Fastexcel極速寫入、低記憶體唯寫、功能受限高效能報表生成

結語

Apache POI 雖然 API 複雜,但只要掌握 WorkbookFactory 統一入口、DataFormatter 格式化、SAX 流式讀取 三大核心,即可應對 90% 的 Excel 讀取需求。對於百萬行以上的大檔案,強烈建議改用 EasyExcel 或 SAX 模式,避免記憶體溢位風險。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved