技術選型:為什麼選 SXSSF?
- SXSSF (Streaming XSSF):只在內存保留滑動窗口(默認 100 行),其餘刷入臨時文件,支援百萬行導出不 OOM
- XSSF:全內存,適合 < 5 萬行
- HSSF:舊 .xls 格式,65535 行限制
ExportExcel 核心類(精簡版)
public class ExportExcel {
private final SXSSFWorkbook wb;
private final Sheet sheet;
private final Map<String, CellStyle> styles = new HashMap<>();
private int rownum = 0;
public ExportExcel(String title, List<String> headers) {
wb = new SXSSFWorkbook(500); // 窗口 500 行
sheet = wb.createSheet("Data");
createStyles();
if (title != null) createTitleRow(title, headers.size());
createHeaderRow(headers);
}
private void createStyles() {
// 標題樣式:大字體、加粗、居中、合併儲存格
// 表頭樣式:深灰背景、白字、加粗、邊框
// 數據樣式:左/中/右對齊、日期格式、數值格式
}
public Row addRow() { return sheet.createRow(rownum++); }
public Cell addCell(Row row, int col, Object val, int align) {
Cell cell = row.createCell(col);
CellStyle style = styles.get("data" + (align >= 1 && align <= 3 ? align : ""));
// 根據類型設置值:String/Integer/Long/Double/Date/Boolean
cell.setCellStyle(style);
return cell;
}
/** 註解驅動導出:實體類字段標註 @ExcelField */
public <E> ExportExcel setDataList(List<E> list) {
for (E e : list) {
Row row = addRow();
// 反射獲取字段值,按 @ExcelField.sort() 排序
// 支援字典轉換、日期格式化、數值精度控制
}
return this;
}
public void write(HttpServletResponse response, String fileName) throws IOException {
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
wb.write(response.getOutputStream());
dispose();
}
public void dispose() { wb.dispose(); } // 釋放臨時文件
}
ImportExcel 核心類
public class ImportExcel {
private final Workbook wb;
private final Sheet sheet;
private final int headerRow;
public ImportExcel(InputStream is, String fileName, int headerRow) throws IOException {
this.headerRow = headerRow;
wb = fileName.endsWith(".xlsx") ? new XSSFWorkbook(is) : new HSSFWorkbook(is);
sheet = wb.getSheetAt(0);
}
public <E> List<E> getDataList(Class<E> clazz) {
List<E> list = new ArrayList<>();
// 解析 @ExcelField 註解構建字段映射
for (int i = headerRow + 1; i <= sheet.getLastRowNum(); i++) {
Row row = sheet.getRow(i);
if (row == null) continue;
E entity = clazz.getDeclaredConstructor().newInstance();
// 遍歷字段,根據類型轉換單元格值
// String/Integer/Long/Double/Date/Boolean + 字典反查
list.add(entity);
}
return list;
}
}
實體類註解定義
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
String title() default "";
int sort() default 0;
String dictType() default ""; // 字典類型,導出時自動轉換
int align() default 0; // 1左 2中 3右
Class<?> fieldType() default void.class; // 自定義類型轉換器
}
使用示例
// 導出
@GetMapping("/export")
public void export(HttpServletResponse response) {
List users = userService.findAll();
List headers = Arrays.asList("ID", "姓名", "部門", "入職日期");
new ExportExcel("用戶列表", headers)
.setDataList(users)
.write(response, "users_" + System.currentTimeMillis() + ".xlsx");
}
// 導入
@PostMapping("/import")
public Result importExcel(@RequestParam MultipartFile file) {
List users = new ImportExcel(file.getInputStream(), file.getOriginalFilename(), 0)
.getDataList(User.class);
userService.batchInsert(users);
return Result.success("導入 " + users.size() + " 條");
}
效能優化關鍵點
| 優化點 | 說明 |
| SXSSF 窗口大小 | 根據行大小調整,建議 100-1000,默認 100 |
| 臨時文件目錄 | -Djava.io.tmpdir=/data/tmp 指定大磁盤分區 |
| 避免重複創建 Style | 預創建樣式 Map 復用,每行不 new CellStyle |
| 流式寫入 Response | 直接 wb.write(response.getOutputStream()),不落地 |
| 導入並發處理 | 大文件分 Sheet 多線程解析,入庫用批量 INSERT |