| 特性 | .xls (HSSF) | .xlsx (XSSF) |
|---|---|---|
| Excel 版本 | 97-2003 | 2007+ |
| 儲存格式 | BIFF 二進位 | OOXML (ZIP + XML) |
| 行數上限 | 65,536 (2^16-1) | 1,048,576 (2^20-1) |
| 列數上限 | 256 (IV) | 16,384 (XFD) |
| POI 前綴 | HSSF* | XSSF* |
| 記憶體佔用 | 較低 | 較高(XML 解析) |
💡 統一介面:
org.apache.poi.ss.usermodel.*(Workbook/Sheet/Row/Cell) 同時支援兩格式,實際類由WorkbookFactory自動判斷
| 操作 | 介面/類 | 關鍵方法 |
|---|---|---|
| 建立/讀取 Workbook | WorkbookFactory, HSSFWorkbook, XSSFWorkbook | create(), write(OutputStream) |
| 建立/取得 Sheet | Sheet | createSheet(name), getSheetAt(idx), getSheet(name) |
| 建立/取得 Row | Row | createRow(rowNum), getRow(rowNum) |
| 建立/取得 Cell | Cell | createCell(colIdx), getCell(colIdx, MissingCellPolicy) |
| 設定儲存格值 | Cell | setCellValue(String/Double/Boolean/Date/Calendar/RichTextString) |
| 樣式 | CellStyle, Font, DataFormat | setCellStyle(), setFont(), setDataFormat() |
| 公式 | Cell | setCellFormula("SUM(A1:A10)") |
| 合併儲存格 | Sheet | addMergedRegion(CellRangeAddress) |
public void exportUserReport(List<User> users, OutputStream out) throws IOException {
try (XSSFWorkbook wb = new XSSFWorkbook()) {
Sheet sheet = wb.createSheet("用戶清單");
// 標題列樣式
CellStyle headerStyle = wb.createCellStyle();
Font headerFont = wb.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 建立標題
Row header = sheet.createRow(0);
String[] cols = {"ID", "姓名", "Email", "建立時間", "啟用"};
for (int i = 0; i < cols.length; i++) {
Cell cell = header.createCell(i);
cell.setCellValue(cols[i]);
cell.setCellStyle(headerStyle);
}
// 資料列
CellStyle dateStyle = wb.createCellStyle();
dateStyle.setDataFormat(wb.createDataFormat().getFormat("yyyy-mm-dd hh:mm"));
for (int i = 0; i < users.size(); i++) {
User u = users.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(u.getId());
row.createCell(1).setCellValue(u.getName());
row.createCell(2).setCellValue(u.getEmail());
Cell dateCell = row.createCell(3);
dateCell.setCellValue(u.getCreatedAt());
dateCell.setCellStyle(dateStyle);
row.createCell(4).setCellValue(u.isEnabled());
}
// 自動欄寬
for (int i = 0; i < cols.length; i++) sheet.autoSizeColumn(i);
wb.write(out);
}
}
public void updateExcel(File file, String sheetName, int rowIdx, int colIdx, String newVal) throws IOException {
try (FileInputStream fis = new FileInputStream(file);
Workbook wb = WorkbookFactory.create(fis)) {
Sheet sheet = wb.getSheet(sheetName);
if (sheet == null) throw new IllegalArgumentException("Sheet 不存在: " + sheetName);
Row row = sheet.getRow(rowIdx);
if (row == null) row = sheet.createRow(rowIdx);
Cell cell = row.getCell(colIdx, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
cell.setCellValue(newVal);
try (FileOutputStream fos = new FileOutputStream(file)) {
wb.write(fos);
}
}
}
// 僅保留最近 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("big.xlsx"));
}
SXSSFWorkbook(windowSize):windowSize 為記憶體保留行數❌
getCell(col)回傳 null → 用getCell(col, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK)❌ 日期儲存為數字 → 讀取時用
DateUtil.isCellDateFormatted(cell)判斷再getDateCellValue()❌ 公式未計算 → 寫入後呼叫
FormulaEvaluator.evaluateAll(wb)或開啟 Excel 自動重算❌ .xls 65535 行限制 → 超過自動切換 .xlsx 或分多 Sheet
❌ 大檔 OOM → 改用 SXSSF 或 EasyExcel (阿里開源,更省記憶體)
<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>