Apache POI 是 Java 操作 Excel 事實標準,支持 .xls (HSSF) 與 .xlsx (XSSF/SXSSF)。本文系統講解環境搭建、核心模型、讀寫基礎、樣式設計、公式計算、大數據流式處理(SXSSF)、常見坑點,並提供生產級工具類封裝。
<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>
<!-- .xlsx 大數據流式寫入需 SXSSF -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-full</artifactId>
<version>5.3.0</version>
</dependency>
| 格式 | 工作簿 | 工作表 | 行 | 單元格 | 行數上限 | 內存模式 |
|---|---|---|---|---|---|---|
.xls | HSSFWorkbook | HSSFSheet | HSSFRow | HSSFCell | 65,536 (216) | 全量 DOM |
.xlsx | XSSFWorkbook | XSSFSheet | XSSFRow | XSSFCell | 1,048,576 (220) | 全量 DOM |
.xlsx (流式) | SXSSFWorkbook | SXSSFSheet | SXSSFRow | SXSSFCell | 無上限 | 滑動窗口 (默認 100 行) |
package com.util;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class ExcelReader {
public static List> readAll(File file) throws IOException {
try (InputStream is = new FileInputStream(file)) {
return readAll(is, file.getName());
}
}
public static List> readAll(InputStream is, String fileName) throws IOException {
Workbook wb = createWorkbook(is, fileName);
try {
List> result = new ArrayList<>();
for (int si = 0; si < wb.getNumberOfSheets(); si++) {
Sheet sheet = wb.getSheetAt(si);
for (Row row : sheet) {
List rowData = new ArrayList<>();
for (Cell cell : row) {
rowData.add(getCellValueAsString(cell));
}
if (!rowData.isEmpty()) result.add(rowData);
}
}
return result;
} finally {
wb.close();
}
}
private static Workbook createWorkbook(InputStream is, String fileName) throws IOException {
String lower = fileName.toLowerCase();
if (lower.endsWith(".xlsx")) return new XSSFWorkbook(is);
if (lower.endsWith(".xls")) return new HSSFWorkbook(is);
throw new IllegalArgumentException("不支持的格式:" + fileName);
}
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 cell.getDateCellValue().toString();
}
// 避免科學計數法,去尾零
double val = cell.getNumericCellValue();
return (val == (long) val) ? String.valueOf((long) val) : String.valueOf(val);
case BOOLEAN: return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return getCellValueAsString(cell.getCachedFormulaResultTypeEnum() != null
? cell : null); // 簡化:實際需評估公式
} catch (Exception e) {
return cell.getCellFormula();
}
case BLANK: return "";
case ERROR: return "#ERROR";
default: return "";
}
}
}
try (XSSFWorkbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("報表");
// 1. 創建字體
Font headerFont = wb.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 12);
headerFont.setColor(IndexedColors.WHITE.getIndex());
Font bodyFont = wb.createFont();
bodyFont.setFontHeightInPoints((short) 11);
// 2. 創建單元格樣式
CellStyle headerStyle = wb.createCellStyle();
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.DARK_BLUE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setBorderBottom(BorderStyle.THIN);
headerStyle.setBorderTop(BorderStyle.THIN);
headerStyle.setBorderLeft(BorderStyle.THIN);
headerStyle.setBorderRight(BorderStyle.THIN);
CellStyle bodyStyle = wb.createCellStyle();
bodyStyle.setFont(bodyFont);
bodyStyle.setAlignment(HorizontalAlignment.LEFT);
bodyStyle.setBorderBottom(BorderStyle.THIN);
bodyStyle.setBorderLeft(BorderStyle.THIN);
bodyStyle.setBorderRight(BorderStyle.THIN);
CellStyle numberStyle = wb.createCellStyle();
numberStyle.cloneStyleFrom(bodyStyle);
numberStyle.setDataFormat(wb.createDataFormat().getFormat("#,##0.00"));
// 3. 寫入表頭
Row headerRow = sheet.createRow(0);
String[] headers = {"ID", "姓名", "金額", "日期"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 4. 寫入數據
for (int r = 0; r < 100; r++) {
Row row = sheet.createRow(r + 1);
Cell c0 = row.createCell(0); c0.setCellValue(r); c0.setCellStyle(bodyStyle);
Cell c1 = row.createCell(1); c1.setCellValue("用戶" + r); c1.setCellStyle(bodyStyle);
Cell c2 = row.createCell(2); c2.setCellValue(r * 100.5); c2.setCellStyle(numberStyle);
Cell c3 = row.createCell(3); c3.setCellValue(new Date()); c3.setCellStyle(bodyStyle);
}
// 5. 自動列寬 (慎用大數據)
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
try (FileOutputStream fos = new FileOutputStream("report.xlsx")) {
wb.write(fos);
}
}
// SXSSFWorkbook 包裝 XSSFWorkbook,滑動窗口默認 100 行
try (SXSSFWorkbook wb = new SXSSFWorkbook(1000)) { // 內存保留 1000 行
Sheet sheet = wb.createSheet("大數據導出");
// 樣式創建同 XSSF (SXSSF 繼承自 XSSF)
for (int r = 0; r < 1_000_000; r++) {
Row row = sheet.createRow(r);
row.createCell(0).setCellValue(r);
row.createCell(1).setCellValue("數據" + r);
// 寫入即刷新到磁盤,內存只保留最近 1000 行
}
// 注意:SXSSF 不支持 autoSizeColumn (需預估寬度)
sheet.setColumnWidth(0, 12 * 256);
sheet.setColumnWidth(1, 30 * 256);
// 臨時文件會寫入磁盤,關閉時自動清理
try (FileOutputStream fos = new FileOutputStream("big_export.xlsx")) {
wb.write(fos);
}
wb.dispose(); // 顯式清理臨時文件 (try-with-resources 會自動調用)
}
autoSizeColumn、shiftRows、getRow 讀取已刷新行java.io.tmpdir,大導出需確保磁盤空間try (XSSFWorkbook wb = new XSSFWorkbook(new FileInputStream("formula.xlsx"))) {
Sheet sheet = wb.getSheetAt(0);
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
for (Row row : sheet) {
for (Cell cell : row) {
if (cell.getCellType() == CellType.FORMULA) {
CellValue cellValue = evaluator.evaluate(cell);
switch (cellValue.getCellType()) {
case NUMERIC: System.out.println(cellValue.getNumberValue()); break;
case STRING: System.out.println(cellValue.getStringValue()); break;
case BOOLEAN: System.out.println(cellValue.getBooleanValue()); break;
case ERROR: System.out.println(FormulaError.forInt(cellValue.getErrorValue()));
}
}
}
}
// 寫回評估結果 (可選)
evaluator.evaluateAll();
try (FileOutputStream fos = new FileOutputStream("evaluated.xlsx")) {
wb.write(fos);
}
}
| 問題 | 原因 | 解決方案 |
|---|---|---|
| 數字顯示為科學計數法 (1.23E7) | 默認 General 格式 | 設置 DataFormat 為 0 或 #,##0 |
| 日期讀出為數字 (44927) | Excel 日期本質是數字 | 用 DateUtil.isCellDateFormatted(cell) 判斷並轉換 |
| 大文件導出 OOM | XSSF 全量內存 | 改用 SXSSF 流式寫入 |
| SXSSF 寫入後文件為空/損壞 | 未調用 wb.write() 或 dispose() | 確保 try-with-resources 正確關閉 |
| .xls 65536 行限制 | BIFF8 格式限制 | 強制用 .xlsx + XSSF/SXSSF |
| 合併單元格邊框消失 | POI 合併區域邊框需單獨設置 | 用 RegionUtil.setBorderXXX() 設置合併區域邊框 |
| 中文亂碼 | 字體不支持 | 設置字體 font.setFontName("微軟雅黑"),確保服務器有字體 |
若專案需極簡 API、極致性能、注解驅動,推薦 EasyExcel:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.0.5</version>
</dependency>
// 讀取
EasyExcel.read(file, DemoData.class, new DemoListener()).sheet().doRead();
// 寫入
EasyExcel.write(file, DemoData.class).sheet("模板").doWrite(dataList);
EasyExcel 內部基於 SAX 解析 + SXSSF,無需手動管理模型、樣式、內存,適合 90% 業務導入導出場景。
總結:Apache POI 功能最全、控制力最強,掌握 SXSSF 流式寫入是大數據導出必修課。日常業務推薦 EasyExcel 提升開發效率;複雜報表、精細樣式、模板填充、公式計算場景,POI 仍是不可替代的底層利器。統一工具類、樣式復用、異步處理、臨時文件清理,是生產級落地關鍵。