Apache POI 是 Java 生態中處理 Microsoft Office 文檔的事實標準庫。本文提供基於 POI 4.0+ 的完整解析方案,覆蓋舊版 .xls (HSSF) 與新版 .xlsx (XSSF) 雙格式,包含多類型單元格處理、大文件流式讀取、異常防禦等工程化實踐。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
public interface ExcelParser {
void parse(InputStream is, SheetHandler handler) throws IOException;
}
public class ExcelParserFactory {
public static ExcelParser createParser(String fileName) {
if (fileName.endsWith(".xlsx")) {
return new XlsxParser();
} else if (fileName.endsWith(".xls")) {
return new XlsParser();
}
throw new IllegalArgumentException("不支持的文件格式: " + fileName);
}
}
public class XlsxParser implements ExcelParser {
@Override
public void parse(InputStream is, SheetHandler handler) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook(is)) {
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
XSSFSheet sheet = wb.getSheetAt(i);
handler.onSheetStart(sheet.getSheetName());
parseSheet(sheet, handler);
handler.onSheetEnd();
}
}
}
private void parseSheet(XSSFSheet sheet, SheetHandler handler) {
for (Row row : sheet) { // 迭代器模式,自動跳過空行
handler.onRowStart(row.getRowNum());
for (Cell cell : row) {
handler.onCell(cell.getColumnIndex(), getCellValue(cell));
}
handler.onRowEnd();
}
}
private Object getCellValue(Cell cell) {
switch (cell.getCellType()) {
case STRING: return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue(); // 日期類型特殊處理
}
return cell.getNumericCellValue();
case BOOLEAN: return cell.getBooleanCellValue();
case FORMULA: return cell.getCellFormula(); // 或評估值: cell.getNumericCellValue()
case BLANK: return null;
case ERROR: return cell.getErrorCellValue();
default: return null;
}
}
}
public class XlsParser implements ExcelParser {
@Override
public void parse(InputStream is, SheetHandler handler) throws IOException {
try (POIFSFileSystem fs = new POIFSFileSystem(is);
HSSFWorkbook wb = new HSSFWorkbook(fs)) {
for (int i = 0; i < wb.getNumberOfSheets(); i++) {
HSSFSheet sheet = wb.getSheetAt(i);
handler.onSheetStart(sheet.getSheetName());
parseSheet(sheet, handler);
handler.onSheetEnd();
}
}
}
// parseSheet/getCellValue 邏輯與 XSSF 完全一致,得益於 SS 統一接口
}
// 內存友好: 僅保留最近 100 行在內存,其餘刷盤
try (SXSSFWorkbook wb = new SXSSFWorkbook(100)) {
// 寫入大量數據...
wb.dispose(); // 臨時文件清理
}
// 只讀流式: XSSFReader + SAX 事件驅動 (最低內存)
OPCPackage pkg = OPCPackage.open(file);
XSSFReader reader = new XSSFReader(pkg);
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(new SheetHandler(sheetIndex));
parser.parse(new InputSource(reader.getSheetsData().next()));
// 單元格樣式數據格式解析
DataFormatter formatter = new DataFormatter(Locale.CHINA);
String formattedValue = formatter.formatCellValue(cell);
// 自動處理: 日期→"2024-01-15"、貨幣→"¥1,234.56"、百分比→"12.34%"
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
CellValue cellValue = evaluator.evaluate(cell);
switch (cellValue.getCellType()) {
case NUMERIC: return cellValue.getNumberValue();
case STRING: return cellValue.getStringValue();
case BOOLEAN: return cellValue.getBooleanValue();
case ERROR: return ErrorEval.getText(cellValue.getErrorValue());
}
try-with-resourcesEncryptedDocumentException (加密文件)、OfficeXmlFileException (損壞文件)public interface SheetHandler {
void onSheetStart(String sheetName);
void onSheetEnd();
void onRowStart(int rowIndex);
void onRowEnd();
void onCell(int colIndex, Object value);
void onError(Exception e); // 統一異常處理
}
public class ExcelImportService {
public void importData(MultipartFile file) throws IOException {
ExcelParser parser = ExcelParserFactory.createParser(file.getOriginalFilename());
parser.parse(file.getInputStream(), new SheetHandler() {
private List<Map<String, Object>> batch = new ArrayList<>();
private String[] headers;
@Override
public void onRowStart(int rowIndex) {
if (rowIndex == 0) return; // 標題行單獨處理
}
@Override
public void onCell(int colIndex, Object value) {
if (rowIndex == 0) {
headers[colIndex] = String.valueOf(value);
} else {
rowData.put(headers[colIndex], value);
}
}
@Override
public void onRowEnd() {
if (rowIndex > 0) {
batch.add(rowData);
if (batch.size() >= 500) {
saveBatch(batch);
batch.clear();
}
}
}
});
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 數字讀成科學計數法 | 默認 getNumericCellValue() 返回 double | 用 DataFormatter 或 DecimalFormat 格式化 |
| 日期讀成數字 | Excel 日期存儲為數字 (1900 起始天數) | DateUtil.isCellDateFormatted() 判斷 + getDateCellValue() |
| 大文件 OOM | XSSF 全量載入 DOM 樹 | SXSSF (寫) / XSSFReader+SAX (讀) |
| 公式結果為 null | 未評估公式 | FormulaEvaluator.evaluate() |
| .xls 讀取失敗 | 用 XSSF 讀 HSSF 文件 | 工廠模式自適應/POIFSFileSystem 識別 |
Apache POI 提供了強大的 Excel 解析能力,關鍵在於:統一 SS 接口屏蔽格式差異、流式 API 解決大文件內存、DataFormatter 處理顯示格式、FormulaEvaluator 計算公式、工廠模式自適應雙格式。掌握這些核心模式,即可構建健壯、高性能的 Excel 導入導出系統。