简介
Excel 操作在后端开发里几乎绕不开,数据导入导出、报表生成都要用到。Apache POI 是 Java 操作 Excel 的标准库,但它的工作簿创建逻辑相对繁琐:文件存不存在要分开处理、加密文件需要单独传密码、大数据量要切换 SXSSFWorkbook 以避免内存溢出,每种场景写法不一样,容易搞混。
Hutool 的 WorkbookUtil 把这些场景统一封装,提供了两套创建方法:createBook 系列用于普通工作簿,createSXSSFBook 系列用于大批量数据写出;同时提供了 Sheet 管理和输出工具方法。核心能力包括:
• 普通工作簿:createBook 系列,支持新建、加载已有文件、加密文件、只读/读写模式 • 流式工作簿:createSXSSFBook 系列,基于 SXSSF 的低内存流式写出,适合十万级以上数据量 • Sheet 管理:getOrCreateSheet 按索引或名称获取或新建 Sheet • 工作簿输出:writeBook 把工作簿内容写出到流
常用 API 详解
1. 普通工作簿创建与加载 - createBook()
API 签名(按场景列举常用重载):
static Workbook createBook(boolean isXlsx)static Workbook createBook(String excelFilePath)static Workbook createBook(String excelFilePath, boolean readOnly)static Workbook createBook(File excelFile)static Workbook createBook(File excelFile, boolean readOnly)static Workbook createBook(File excelFile, String password)static Workbook createBook(File excelFile, String password, boolean readOnly)static Workbook createBook(InputStream in)static Workbook createBook(InputStream in, String password)static Workbook createBookForWriter(File excelFile)功能: createBook 根据入参的不同,覆盖以下几种场景:
createBook(boolean isXlsx):创建全新的空白工作簿,传 true 生成 .xlsx 格式(XSSFWorkbook),传 false 生成 .xls 格式(HSSFWorkbook),不关联磁盘文件,适合临时构造数据后写出到流。
createBook(String/File excelFile):加载已有 Excel 文件,默认读写模式;如果文件不存在,则新建一个空工作簿。带 boolean readOnly 参数的重载可以明确指定只读,只读模式会使用 XSSFWorkbook 的 read-only 优化,内存占用更低。
createBook(File/InputStream, String password):加载有密码保护的 Excel 文件,password 为 null 时等同于无密码版本。
createBookForWriter(File excelFile):专门用于写出场景,excelFile 为 null 时返回空白 xlsx 工作簿;如果文件存在,加载后用于追加写入;如果文件不存在,创建空白工作簿。
示例:
import cn.hutool.poi.excel.WorkbookUtil;import org.apache.poi.ss.usermodel.Workbook;import java.io.File;import java.io.FileInputStream;public class CreateBookDemo { public static void main(String[] args) throws Exception { // 新建空白 xlsx 工作簿 Workbook xlsxBook = WorkbookUtil.createBook(true); System.out.println(xlsxBook.getClass().getSimpleName()); // XSSFWorkbook // 新建空白 xls 工作簿 Workbook xlsBook = WorkbookUtil.createBook(false); System.out.println(xlsBook.getClass().getSimpleName()); // HSSFWorkbook // 加载已有文件(文件不存在时自动新建) Workbook fromFile = WorkbookUtil.createBook("/data/report.xlsx"); // 只读方式加载,适合只读取数据的场景 Workbook readOnly = WorkbookUtil.createBook( new File("/data/report.xlsx"), true ); // 加载有密码的 Excel 文件 Workbook encrypted = WorkbookUtil.createBook( new File("/data/secret.xlsx"), "mypassword" ); // 从流加载(如从数据库取到的文件字节流) try (FileInputStream fis = new FileInputStream("/data/report.xlsx")) { Workbook fromStream = WorkbookUtil.createBook(fis); } // 用于写出场景的工作簿:文件存在则加载,不存在则新建 Workbook forWriter = WorkbookUtil.createBookForWriter(new File("/data/output.xlsx")); }}实战场景: 判断文件是否存在,存在就加载追加数据,不存在就新建:
public Workbook openOrCreate(String filePath) { return WorkbookUtil.createBook(filePath); // 内部自动处理两种情况}2. 流式工作簿 - createSXSSFBook()
API 签名(按场景列举常用重载):
static SXSSFWorkbook createSXSSFBook()static SXSSFWorkbook createSXSSFBook(int rowAccessWindowSize)static SXSSFWorkbook createSXSSFBook(int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable)static SXSSFWorkbook createSXSSFBook(String excelFilePath)static SXSSFWorkbook createSXSSFBook(String excelFilePath, boolean readOnly)static SXSSFWorkbook createSXSSFBook(File excelFile)static SXSSFWorkbook createSXSSFBook(File excelFile, boolean readOnly)static SXSSFWorkbook createSXSSFBook(File excelFile, String password)static SXSSFWorkbook createSXSSFBook(File excelFile, String password, boolean readOnly)static SXSSFWorkbook createSXSSFBook(InputStream in)static SXSSFWorkbook createSXSSFBook(InputStream in, String password)功能: SXSSFWorkbook 是 POI 专门为大数据量写出设计的流式实现。普通 XSSFWorkbook 把所有行都保存在内存里,几万行数据就可能 OOM;SXSSFWorkbook 只保留内存中最近的 rowAccessWindowSize 行(默认 100),超出窗口的行写入临时文件,内存占用基本恒定。代价是已经滚出窗口的行不能再修改。
createSXSSFBook():创建默认配置的 SXSSFWorkbook,内存窗口 100 行。
createSXSSFBook(int rowAccessWindowSize):指定内存窗口大小,窗口越大内存占用越高,但随机访问最近行的范围越大。
createSXSSFBook(int rowAccessWindowSize, boolean compressTmpFiles, boolean useSharedStringsTable):compressTmpFiles 为 true 时临时文件会 gzip 压缩,磁盘占用更小;useSharedStringsTable 为 true 时字符串会共享存储(同 XSSFWorkbook 行为),但会占用更多内存。
其余重载与 createBook 类似,支持从已有文件或流加载,区别是返回 SXSSFWorkbook 类型。
示例:
import cn.hutool.poi.excel.WorkbookUtil;import org.apache.poi.xssf.streaming.SXSSFWorkbook;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.usermodel.Row;public class SXSSFDemo { public static void main(String[] args) throws Exception { // 默认 100 行内存窗口 SXSSFWorkbook workbook = WorkbookUtil.createSXSSFBook(); Sheet sheet = workbook.createSheet("大数据"); // 写入 10 万行不会 OOM for (int i = 0; i < 100000; i++) { Row row = sheet.createRow(i); row.createCell(0).setCellValue("行 " + i); row.createCell(1).setCellValue(i * 1.5); } // 指定内存窗口为 500 行 SXSSFWorkbook bigWindow = WorkbookUtil.createSXSSFBook(500); // 临时文件压缩,减少磁盘占用 SXSSFWorkbook compressed = WorkbookUtil.createSXSSFBook(100, true, false); workbook.close(); bigWindow.close(); compressed.close(); }}实战场景: 导出超大数据集时,明确使用 SXSSF 避免 OOM:
public SXSSFWorkbook createLargeExport() { // 窗口 200 行,临时文件压缩 return WorkbookUtil.createSXSSFBook(200, true, false);}3. Sheet 管理 - getOrCreateSheet()
API 签名:
static Sheet getOrCreateSheet(Workbook book, int sheetIndex)static Sheet getOrCreateSheet(Workbook book, String sheetName)功能: 按索引或名称获取 Sheet,如果对应 Sheet 不存在则自动创建。按索引获取时,若 index 超出当前 Sheet 数量,会以默认名称新建;按名称获取时,若名称不存在则创建同名 Sheet。避免了手写"先判断是否存在,不存在再创建"的重复代码。
示例:
import cn.hutool.poi.excel.WorkbookUtil;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.usermodel.Workbook;public class GetOrCreateSheetDemo { public static void main(String[] args) { Workbook workbook = WorkbookUtil.createBook(true); // 按名称获取,不存在则创建 Sheet sheet1 = WorkbookUtil.getOrCreateSheet(workbook, "一月数据"); Sheet sheet2 = WorkbookUtil.getOrCreateSheet(workbook, "二月数据"); System.out.println(workbook.getNumberOfSheets()); // 2 // 再次按相同名称获取,返回已有的 Sheet,不会重复创建 Sheet same = WorkbookUtil.getOrCreateSheet(workbook, "一月数据"); System.out.println(same == sheet1); // true(同一个对象) // 按索引获取,索引 0 是第一个 Sheet Sheet byIndex = WorkbookUtil.getOrCreateSheet(workbook, 0); System.out.println(byIndex.getSheetName()); // 一月数据 workbook.close(); }}实战场景: 多月份数据分 Sheet 写出时,按月份名称自动管理 Sheet:
public Sheet getMonthSheet(Workbook workbook, int month) { return WorkbookUtil.getOrCreateSheet(workbook, month + "月数据");}4. 空判断与输出 - isEmpty() / writeBook()
API 签名:
static boolean isEmpty(Sheet sheet)static void writeBook(Workbook book, OutputStream out)功能:
isEmpty(Sheet sheet):判断 Sheet 是否为空(没有任何行数据),返回 true 表示空表。常用于数据写入前的状态检查,或者判断模板 Sheet 是否未被填充。
writeBook(Workbook book, OutputStream out):把工作簿内容写出到指定输出流,不关闭流。通常配合 createBook / createSXSSFBook 使用,写完所有数据后统一冲出,适合需要把 Excel 输出到 HTTP 响应或自定义流的场景。
示例:
import cn.hutool.poi.excel.WorkbookUtil;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.ss.usermodel.Workbook;import java.io.FileOutputStream;public class WriteBookDemo { public static void main(String[] args) throws Exception { Workbook workbook = WorkbookUtil.createBook(true); Sheet sheet = WorkbookUtil.getOrCreateSheet(workbook, "数据"); System.out.println(WorkbookUtil.isEmpty(sheet)); // true,刚创建还没数据 // 写入几行数据 sheet.createRow(0).createCell(0).setCellValue("姓名"); sheet.createRow(1).createCell(0).setCellValue("张三"); System.out.println(WorkbookUtil.isEmpty(sheet)); // false,已有数据 // 输出到文件流(不关闭流,由调用方管理) try (FileOutputStream out = new FileOutputStream("/tmp/output.xlsx")) { WorkbookUtil.writeBook(workbook, out); } workbook.close(); }}快速使用指南
Maven 依赖
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-poi</artifactId> <version>5.8.26</version></dependency><dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.3</version></dependency>使用场景速查
// 新建空白工作簿Workbook book = WorkbookUtil.createBook(true); // xlsxWorkbook book = WorkbookUtil.createBook(false); // xls// 加载已有文件Workbook book = WorkbookUtil.createBook("/path/to.xlsx");Workbook book = WorkbookUtil.createBook(file, true); // 只读模式// 加载有密码文件Workbook book = WorkbookUtil.createBook(file, "password");// 大数据量写出(推荐 SXSSF)SXSSFWorkbook book = WorkbookUtil.createSXSSFBook();SXSSFWorkbook book = WorkbookUtil.createSXSSFBook(500); // 自定义内存窗口// Sheet 操作Sheet sheet = WorkbookUtil.getOrCreateSheet(book, "Sheet名");// 输出到流WorkbookUtil.writeBook(book, outputStream);完整实战案例:十万条订单数据导出 Excel
常见的 Excel 导出需求,在数据量较小(几千条)时用 XSSFWorkbook 没问题,但数据量上到几万甚至十万以上,就必须切换到 SXSSFWorkbook,否则 JVM 堆内存会被撑爆,导致 OOM。
下面实现一个订单数据批量导出工具,使用 createSXSSFBook 控制内存窗口,分月份建 Sheet,最后通过 writeBook 推到 HTTP 响应流:
import cn.hutool.poi.excel.WorkbookUtil;import org.apache.poi.ss.usermodel.Row;import org.apache.poi.ss.usermodel.Sheet;import org.apache.poi.xssf.streaming.SXSSFWorkbook;import java.io.IOException;import java.io.OutputStream;import java.util.List;import java.util.Map;import java.util.TreeMap;/** * 大批量订单数据 Excel 导出工具 * 使用 SXSSFWorkbook 控制内存,避免 OOM */public class LargeOrderExporter { static class Order { String orderId; String productName; int quantity; double amount; String month; // 格式 "2024-01" Order(String orderId, String productName, int quantity, double amount, String month) { this.orderId = orderId; this.productName = productName; this.quantity = quantity; this.amount = amount; this.month = month; } } /** * 导出订单数据到输出流 * 按月份分 Sheet,每个 Sheet 首行为表头 * * @param orders 订单列表(可以是十万条以上) * @param outputStream 输出流(HTTP响应流或文件流) */ public static void export(List<Order> orders, OutputStream outputStream) throws IOException { // 内存窗口 200 行,临时文件压缩节省磁盘空间 SXSSFWorkbook workbook = WorkbookUtil.createSXSSFBook(200, true, false); // 按月份分组,TreeMap 保证月份有序 Map<String, List<Order>> byMonth = new TreeMap<>(); for (Order order : orders) { byMonth.computeIfAbsent(order.month, k -> new java.util.ArrayList<>()).add(order); } for (Map.Entry<String, List<Order>> entry : byMonth.entrySet()) { String month = entry.getKey(); List<Order> monthOrders = entry.getValue(); // 按月份名称获取或新建 Sheet Sheet sheet = WorkbookUtil.getOrCreateSheet(workbook, month); // 检查是否为空(防止重复写表头) if (WorkbookUtil.isEmpty(sheet)) { // 写入表头 Row header = sheet.createRow(0); header.createCell(0).setCellValue("订单号"); header.createCell(1).setCellValue("商品名称"); header.createCell(2).setCellValue("数量"); header.createCell(3).setCellValue("金额(元)"); } int rowIndex = sheet.getLastRowNum() + 1; for (Order order : monthOrders) { Row row = sheet.createRow(rowIndex++); row.createCell(0).setCellValue(order.orderId); row.createCell(1).setCellValue(order.productName); row.createCell(2).setCellValue(order.quantity); row.createCell(3).setCellValue(order.amount); } System.out.println("月份 [" + month + "] 写出完成,共 " + monthOrders.size() + " 条"); } // 统一写出到流,不关闭流 WorkbookUtil.writeBook(workbook, outputStream); workbook.close(); System.out.println("导出完成,共 " + orders.size() + " 条订单"); } public static void main(String[] args) throws Exception { // 模拟 10 万条数据 List<Order> orders = new java.util.ArrayList<>(); String[] months = {"2024-01", "2024-02", "2024-03"}; String[] products = {"手冲咖啡", "拿铁", "美式", "卡布奇诺"}; for (int i = 0; i < 100000; i++) { orders.add(new Order( "ORD-" + String.format("%08d", i), products[i % products.length], (i % 5) + 1, ((i % 10) + 1) * 25.0, months[i % months.length] )); } try (java.io.FileOutputStream fos = new java.io.FileOutputStream("/tmp/orders.xlsx")) { export(orders, fos); } }}整个导出流程的关键点:
createSXSSFBook(200, true, false) 控制内存中最多保留 200 行,超出的行写入临时文件(启用压缩),10 万条数据也不会 OOM。
getOrCreateSheet 按月份名称自动管理多个 Sheet,不需要维护索引变量。
isEmpty 在写表头前做检查,防止在 Sheet 已有数据的情况下重复写入表头行。
writeBook 最后统一把所有内容写出到流,不关闭流,由调用方的 try-with-resources 来负责关闭,资源管理职责清晰。
常见问题
Q:createBook 和 createSXSSFBook 加载同一个文件,有什么区别?A:createBook 加载后得到 XSSFWorkbook,所有行都在内存里,可以随机读写任意行;createSXSSFBook 加载后得到 SXSSFWorkbook,只有最近的 rowAccessWindowSize 行在内存里,超出窗口的行不能再修改。只需要追加写入时用 SXSSF;需要修改已有行的内容时必须用 XSSFWorkbook。
Q:readOnly 模式和正常模式加载内存占用差多少?A:只读模式内部使用 XSSFWorkbook 的轻量读取优化,对于大文件(几十 MB 以上)内存节省效果明显,通常能节省 30%~50% 左右。只要不需要修改内容,建议加上 readOnly = true。
Q:writeBook 不关闭流,使用后会不会资源泄漏?A:writeBook 设计上故意不关闭流,因为流的生命周期应该由创建它的地方管理(调用方)。把 outputStream 放在 try-with-resources 里,writeBook 写完后退出 try 块时流自动关闭,不会泄漏。
Q:SXSSFWorkbook 用完后为什么要调用 dispose() 或 close()?A:SXSSFWorkbook 把超出内存窗口的行写到磁盘临时文件,这些文件需要手动清理。调用 workbook.close() 会自动清理临时文件(内部会调用 dispose())。如果不调用,临时文件会一直占用磁盘空间直到 JVM 退出。
总结
WorkbookUtil 把工作簿的创建、加载和管理统一成了一套静态方法,按数据量和场景选择合适的入口:
数据量是选择 createBook 还是 createSXSSFBook 的核心判断标准:万条以下用 createBook 足够,十万条以上必须切换 createSXSSFBook,否则迟早 OOM。
运行效果:
END
夜雨聆风