使用 Apache POI 实现 Excel 文件读写(导入 / 导出)操作的工具类
一、介绍
Apache POI 是 Apache 基金会提供的用于操作 Microsoft Office 文档的 Java 库,支持 Excel(.xls/.xlsx)、Word、PPT 等格式。本文实现的工具类基于 Apache POI,封装了 Excel 文件的导入(读取)和导出(写入)核心功能,兼容 Excel 97-2003(.xls)和 Excel 2007+(.xlsx)格式,适用于常见的 Excel 数据处理场景。
二、实现
1.依赖
xml
<!-- Excel 2007+ (.xlsx) 核心依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!-- Excel 97-2003 (.xls) 支持 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<!-- 可选:处理公式单元格 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
JDK 版本与 Apache POI 适配关系
| Apache POI 版本 | 最低 JDK 版本 | 推荐 JDK 版本 | 备注 |
|---|---|---|---|
| 3.x 系列(如 3.17、3.19) | JDK 6 | JDK 6/7/8 | 兼容老项目,已停止维护 |
| 4.x 系列(如 4.1.2、4.2.2) | JDK 8 | JDK 8/11 | 主流稳定版本,兼容大部分场景 |
| 5.x 系列(如 5.2.0、5.2.5) | JDK 11 | JDK 11/17 | 最新特性支持,推荐 JDK 11+ |
| 5.2.3+ | JDK 11 | JDK 11/17/21 | 适配更高版本 JDK,支持模块化 |
三、实现
java
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Excel 读写工具类,支持.xls(Excel 97-2003)和.xlsx(Excel 2007+)格式
*/
public class ExcelUtils {
// ==================== 读取 Excel(导入) ====================
/**
* 读取 Excel 文件内容(文件路径)
* @param filePath Excel 文件路径
* @return 每行数据以 Map 存储(key=表头,value=单元格值)
* @throws IOException IO 异常
*/
public static List<Map<String, Object>> readExcel(String filePath) throws IOException {
return readExcel(new FileInputStream(new File(filePath)));
}
/**
* 读取 Excel 输入流内容
* @param inputStream Excel 输入流
* @return 解析后的数据列表
* @throws IOException IO 异常
*/
public static List<Map<String, Object>> readExcel(InputStream inputStream) throws IOException {
List<Map<String, Object>> dataList = new ArrayList<>();
Workbook workbook = null;
try {
// 自动识别 Excel 格式并创建 Workbook
workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0); // 读取第一个工作表
if (sheet == null || sheet.getLastRowNum() < 0) {
return dataList; // 空表格直接返回
}
// 获取表头行(第一行)
Row headerRow = sheet.getRow(0);
int headerCellCount = headerRow.getPhysicalNumberOfCells();
// 遍历数据行(从第二行开始)
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row dataRow = sheet.getRow(rowNum);
if (dataRow == null) continue; // 跳过空行
Map<String, Object> rowData = new HashMap<>();
for (int cellNum = 0; cellNum < headerCellCount; cellNum++) {
Cell headerCell = headerRow.getCell(cellNum);
Cell dataCell = dataRow.getCell(cellNum);
String header = getCellValue(headerCell); // 表头名称
Object value = getCellValue(dataCell); // 单元格值
rowData.put(header, value);
}
dataList.add(rowData);
}
} finally {
// 关闭资源
if (workbook != null) workbook.close();
inputStream.close();
}
return dataList;
}
// ==================== 写入 Excel(导出) ====================
/**
* 导出 Excel 文件(文件路径)
* @param dataList 导出数据(List<Map>,key=表头,value=单元格值)
* @param headers 表头数组(与 dataList 的 key 对应)
* @param filePath 输出文件路径(.xls 或.xlsx 结尾)
* @throws IOException IO 异常
*/
public static void writeExcel(List<Map<String, Object>> dataList, String[] headers, String filePath) throws IOException {
boolean isXlsx = filePath.endsWith(".xlsx");
try (FileOutputStream outputStream = new FileOutputStream(new File(filePath))) {
writeExcel(dataList, headers, outputStream, isXlsx);
}
}
/**
* 导出 Excel 到输出流
* @param dataList 导出数据
* @param headers 表头数组
* @param outputStream 输出流
* @param isXlsx 是否为.xlsx 格式(true=.xlsx,false=.xls)
* @throws IOException IO 异常
*/
public static void writeExcel(List<Map<String, Object>> dataList, String[] headers,
OutputStream outputStream, boolean isXlsx) throws IOException {
// 创建 Workbook(根据格式选择 XSSF 或 HSSF)
Workbook workbook = isXlsx ? new XSSFWorkbook() : new HSSFWorkbook();
Sheet sheet = workbook.createSheet("Sheet1"); // 创建工作表
// 1. 创建表头行
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
sheet.autoSizeColumn(i); // 自动调整列宽
}
// 2. 填充数据行
for (int rowNum = 0; rowNum < dataList.size(); rowNum++) {
Row dataRow = sheet.createRow(rowNum + 1);
Map<String, Object> rowData = dataList.get(rowNum);
for (int cellNum = 0; cellNum < headers.length; cellNum++) {
String header = headers[cellNum];
Object value = rowData.get(header);
Cell cell = dataRow.createCell(cellNum);
setCellValue(cell, value); // 设置单元格值
}
}
// 写入输出流并关闭资源
workbook.write(outputStream);
workbook.close();
}
// ==================== 辅助方法 ====================
/**
* 获取单元格值(兼容不同数据类型)
* @param cell 单元格对象
* @return 单元格值(统一转为字符串)
*/
private static String getCellValue(Cell cell) {
if (cell == null) return "";
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
// 处理日期格式
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
// 处理数字(避免科学计数法)
return String.valueOf((long) cell.getNumericCellValue());
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 计算公式结果
return cell.getCellFormula() + "=" + cell.getNumericCellValue();
default:
return "";
}
}
/**
* 设置单元格值(兼容不同数据类型)
* @param cell 单元格对象
* @param value 待设置的值
*/
private static void setCellValue(Cell cell, Object value) {
if (value == null) {
cell.setCellValue("");
return;
}
if (value instanceof String) {
cell.setCellValue((String) value);
} else if (value instanceof Number) {
cell.setCellValue(((Number) value).doubleValue());
} else if (value instanceof Boolean) {
cell.setCellValue((Boolean) value);
} else if (value instanceof java.util.Date) {
cell.setCellValue((java.util.Date) value);
} else {
cell.setCellValue(value.toString());
}
}
}
//WorkbookFactory.create() 是 Apache POI 4.x+ 提供的自动识别格式方法,简化了格式判断逻辑。
四、说明
-
核心
Workbook:Excel 文档的顶层对象,XSSFWorkbook对应.xlsx,HSSFWorkbook对应.xls。Sheet:工作表对象,一个 Workbook 可包含多个 Sheet。Row:行对象,通过 Sheet 创建或获取。Cell:单元格对象,通过 Row 创建或获取,需处理不同数据类型(字符串、数字、日期等)。
-
思路
- 读取 Excel :先解析表头行,再逐行读取数据,将每行数据封装为
Map<表头, 值>。 - 写入 Excel:先创建表头,再遍历数据列表填充行数据,自动适配.xls/.xlsx 格式。
- 辅助方法 :
getCellValue统一解析单元格值,setCellValue支持多种数据类型写入。
- 读取 Excel :先解析表头行,再逐行读取数据,将每行数据封装为
-
补充
- 空值处理:读取时跳过空行,写入时空值转为空字符串,避免单元格类型异常。
- 日期处理 :通过
DateUtil.isCellDateFormatted识别日期格式单元格,避免数字形式的日期错误。 - 资源关闭 :使用
try-finally或try-with-resources确保 Workbook 和流资源关闭,防止内存泄漏。
五、扩展
多 Sheet 支持:修改工具类,支持读取指定 Sheet 或遍历所有 Sheet。
java
// 读取指定名称的 Sheet
public static List<Map<String, Object>> readExcel(String filePath, String sheetName) throws IOException {
Workbook workbook = WorkbookFactory.create(new FileInputStream(filePath));
Sheet sheet = workbook.getSheet(sheetName);
// 后续逻辑同单 Sheet 读取...
}
样式定制:添加单元格样式(字体、颜色、对齐方式等),示例:
java
// 创建样式
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
headerRow.getCell(0).setCellStyle(style); // 表头加粗
大数据量优化 :对于超大 Excel(百万行级),使用 SXSSFWorkbook(.xlsx)或 HSSFWorkbook 分批写入,避免内存溢出:
java
// 超大文件导出(SXSSF 支持)
Workbook workbook = new SXSSFWorkbook(100); // 内存中保留 100 行,超出后写入磁盘
六、其他
Excel 格式区别:
.xls(Excel 97-2003 格式)
- 核心特性:基于二进制格式(BIFF 格式)存储,是早期 Excel 版本的默认格式。
- 容量限制 :最大支持 65536 行 、256 列,无法处理超大规模数据。
- 兼容性:兼容所有 Excel 版本,但在新版 Excel 中打开时可能提示 "兼容模式"。
- 文件大小:相同数据下文件体积更大,且易受二进制文件损坏影响。
- 功能限制:不支持新版 Excel 特性(如条件格式、切片器、XML 数据映射等)。
.xlsx(Excel 2007+ 格式)
- 核心特性:基于 Office Open XML(OOXML)标准,采用 XML 压缩格式存储。
- 容量限制 :最大支持 1048576 行 、16384 列,可处理海量数据。
- 兼容性:需 Excel 2007 及以上版本支持,低版本需安装兼容包。
- 文件大小:因 XML 压缩机制,文件体积更小,且损坏后更易修复。
- 功能支持:支持新版 Excel 所有特性(如公式扩展、样式定制、数据透视表增强等)。
单元格类型:Excel 单元格支持 6 种类型:STRING(字符串)、NUMERIC(数字 / 日期)、BOOLEAN(布尔)、FORMULA(公式)、BLANK(空)、ERROR(错误),需针对性解析。
| 单元格类型 | 初始读取的数据类型 | 示例场景 |
|---|---|---|
NUMERIC |
数值型(double/long) | 整数、小数、日期(特殊) |
STRING |
字符串(String) | 文本、手动输入的数字 |
BOOLEAN |
布尔值(boolean) | TRUE/FALSE |
FORMULA |
公式计算结果(类型动态) | 如 =A1+B1 的结果 |
BLANK |
空字符串("") | 空单元格 |