專案背景
企業級系統常需「導出 Excel 報表」「匯入 Excel 資料」。Apache POI 是純 Java 實現、無需 Office 安裝、跨平台的標杆庫。本文涵蓋 HSSF (Excel 97-2003 `.xls`) 與 XSSF (Excel 2007+ `.xlsx`) 核心 API、樣式、大數據流式處理、中文支援、遷移建議。
Maven 依賴 (POI 5.3.0)
<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>
核心物件模型
| 介面/類 | 對應 Excel 概念 | HSSF 實現 | XSSF 實現 |
| `Workbook` | 整個活頁簿 | `HSSFWorkbook` | `XSSFWorkbook` |
| `Sheet` | 工作表 | `HSSFSheet` | `XSSFSheet` |
| `Row` | 列 | `HSSFRow` | `XSSFRow` |
| `Cell` | 儲存格 | `HSSFCell` | `XSSFCell` |
| `CellStyle` | 儲存格樣式 | `HSSFCellStyle` | `XSSFCellStyle` |
| `Font` | 字型 | `HSSFFont` | `XSSFFont` |
| `DataFormat` | 資料格式 (日期、貨幣等) | `HSSFDataFormat` | `XSSFDataFormat` |
寫入 Excel 完整範例 (.xlsx)
try (XSSFWorkbook wb = new XSSFWorkbook();
FileOutputStream fos = new FileOutputStream("report.xlsx")) {
XSSFSheet sheet = wb.createSheet("銷售報表");
// 1. 樣式定義
XSSFCellStyle headerStyle = wb.createCellStyle();
XSSFFont headerFont = wb.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 12);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
XSSFCellStyle dataStyle = wb.createCellStyle();
dataStyle.setBorderBottom(BorderStyle.THIN);
dataStyle.setDataFormat(wb.createDataFormat().getFormat("#,##0.00"));
// 2. 表頭
Row header = sheet.createRow(0);
String[] cols = {"產品", "單價", "數量", "金額", "日期"};
for (int i = 0; i < cols.length; i++) {
Cell cell = header.createCell(i);
cell.setCellValue(cols[i]);
cell.setCellStyle(headerStyle);
}
// 3. 資料列 (模擬 10000 列)
for (int r = 1; r <= 10000; r++) {
Row row = sheet.createRow(r);
row.createCell(0).setCellValue("商品" + r);
row.createCell(1).setCellValue(100 + r * 0.5);
row.createCell(2).setCellValue(r % 100);
Cell amt = row.createCell(3);
amt.setCellFormula("B" + (r+1) + "*C" + (r+1));
amt.setCellStyle(dataStyle);
Cell dt = row.createCell(4);
dt.setCellValue(new java.util.Date());
dt.setCellStyle(dataStyle);
}
// 4. 自動欄寬
for (int i = 0; i < cols.length; i++) sheet.autoSizeColumn(i);
wb.write(fos);
}
讀取 Excel (事件驅動 SAX 模式,低記憶體)
// 僅示意 XSSF SAX 解析器
OPCPackage pkg = OPCPackage.open("big.xlsx");
XSSFReader reader = new XSSFReader(pkg);
StylesTable styles = reader.getStylesTable();
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
XMLReader parser = XMLReaderFactory.createXMLReader();
parser.setContentHandler(new XSSFSheetXMLHandler(styles, strings, new SheetContentsHandler() {
@Override
public void startRow(int rowNum) { /* 新列 */ }
@Override
public void cell(String cellRef, String formattedValue, XSSFComment comment) {
System.out.println(cellRef + " = " + formattedValue);
}
@Override
public void endRow(int rowNum) { /* 列結束 */ }
}, false));
parser.parse(new InputSource(reader.getSheetsData().next()));
中文支援關鍵設定
- **字型註冊** (PDF/圖片導出需):`Font font = wb.createFont(); font.setFontName("Microsoft YaHei");` 需系統安裝該字型。
- **編碼**:POI 內部使用 Unicode,讀寫 `.xlsx` 無編碼問題;`.xls` (HSSF) 需 `wb = new HSSFWorkbook();` 直接支援雙位元組字符。
- **公式計算**:`FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); evaluator.evaluateAll();`
大數據量優化策略 (百萬列+)
| 策略 | 適用場景 | 記憶體佔用 |
| SXSSF (Streaming) | 僅寫入、不需隨機讀取 | ~100MB (固定視窗) |
| XSSF SAX 事件解析 | 僅讀取、不需修改 | ~50MB |
| 分批寫入 + 定期 flush | 需隨機修改 | 視批次大小 |
// SXSSF 範例:保留最近 100 行在記憶體,其餘刷入磁碟
try (SXSSFWorkbook wb = new SXSSFWorkbook(100)) {
Sheet sheet = wb.createSheet();
for (int i = 0; i < 1_000_000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("Row " + i);
}
wb.write(new FileOutputStream("huge.xlsx"));
}
常見坑與避雷指南
| 坑點 | 症狀 | 修正 |
| CellStyle 重複建立 | 樣式表超過 64k 限制 (`.xls`) / 檔案膨脹 | 統一建立樣式對象,複用;或用 `CellUtil.getCellStyle()` |
| 日期顯示為數字 | 未設定 DataFormat | `style.setDataFormat(wb.createDataFormat().getFormat("yyyy-mm-dd"))` |
| 公式不計算 | Excel 開啟顯示 0 | 寫入前呼叫 `evaluator.evaluateAll()` 或開啟時手動重新計算 |
| 合併儲存格邊框缺失 | 僅第一格有邊框 | 對合併區域所有儲存格單獨套用邊框樣式 |
現代化替代庫對比
| 庫 | 授權 | 特點 |
| Apache POI | Apache 2.0 | 功能最全、社群最大、支援 .xls/.xlsx/圖表/樞紐分析表 |
| EasyExcel (Alibaba) | Apache 2.0 | 基於 POI 封裝、記憶體友好、註解驅動、適合導入導出業務 |
| JExcelApi (jxl) | LGPL | 僅支援 .xls、已停更、不建議新專案使用 |
| Fastexcel | Apache 2.0 | 純寫入、極速、無樣式支援、適合超大報表 |
建議:**新專案首選 EasyExcel** (業務導入導出) 或 **POI SXSSF** (複雜報表);避開 JXL。