目录
- 环境准备
- 添加 Maven 依赖
- 了解 POI 模块
- 核心概念
Workbook(工作簿)Sheet(工作表)Row(行)Cell(单元格)
- 基础操作
- 创建 Excel 文件 (XLSX / XLS)
- 读取 Excel 文件
- 修改现有 Excel 文件
- 设置单元格样式 (字体、颜色、边框等)
- 进阶操作
- 处理日期
- 处理公式
- 合并单元格
- 设置列宽和行高
- 单元格数据类型
- 性能优化 (SXSSF)
处理大数据量
(图片来源网络,侵删) - 总结与最佳实践
环境准备
添加 Maven 依赖
在你的 pom.xml 文件中添加以下依赖。推荐使用最新的稳定版本。
<dependencies>
<!-- Apache POI for XLSX (Office 2007+) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <!-- 请使用最新版本 -->
</dependency>
<!-- Required for parsing OOXML (XLSX) files -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version> <!-- 请使用最新版本 -->
</dependency>
<!-- Required for OPC package handling (XLSX) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.5</version> <!-- 请使用最新版本 -->
</dependency>
<!-- Optional: For parsing XLS (Legacy Office 97-2003) format -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
了解 POI 模块
poi: 核心,用于处理.xls格式 (Excel 97-2003)。poi-ooxml: 用于处理.xlsx(Excel 2007+) 和.xlsm(带宏的 Excel) 格式。poi-ooxml-lite:poi-ooxml的轻量级版本,如果不需要 XWPF (Word) 和 XSLF (PPT) 可以只加这个。poi-scratchpad: 提供一些实验性或遗留格式的支持。
推荐做法: 即使你只处理 .xlsx,也建议同时引入 poi 和 poi-ooxml,因为它们之间有依赖关系。
核心概念
理解 POI 操作 Excel 的“树状结构”至关重要:
Workbook(工作簿): 代表整个 Excel 文件,一个 Excel 文件就是一个Workbook对象。XSSFWorkbook: 用于.xlsx文件。HSSFWorkbook: 用于.xls文件。
Sheet(工作表): 代表Workbook中的一个表(Sheet1, Sheet2...)。Row(行): 代表Sheet中的一行,行号从0开始。Cell(单元格): 代表Row中的一个格子,列号从0开始。
操作流程通常是:创建/获取 Workbook -> 创建/获取 Sheet -> 创建/获取 Row -> 创建/获取 Cell -> 设置/获取值。

基础操作
创建 Excel 文件 (XLSX)
下面是一个完整的示例,创建一个 .xlsx 文件,写入一些数据,并设置基本样式。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
public class CreateExcelExample {
public static void main(String[] args) {
// 1. 创建一个 XSSFWorkbook 对象 (代表 .xlsx 文件)
Workbook workbook = new XSSFWorkbook();
// 2. 创建一个工作表,名称为 "员工信息"
Sheet sheet = workbook.createSheet("员工信息");
// 3. 创建标题行 (第0行)
Row headerRow = sheet.createRow(0);
// 4. 创建标题单元格并设置值
CellStyle headerStyle = createHeaderStyle(workbook); // 创建标题样式
createCell(headerRow, 0, "员工ID", headerStyle);
createCell(headerRow, 1, "姓名", headerStyle);
createCell(headerRow, 2, "部门", headerStyle);
createCell(headerRow, 3, "入职日期", headerStyle);
// 5. 创建数据行
Row dataRow1 = sheet.createRow(1);
dataRow1.createCell(0).setCellValue(1001);
dataRow1.createCell(1).setCellValue("张三");
dataRow1.createCell(2).setCellValue("研发部");
dataRow1.createCell(3).setCellValue(new java.util.Date()); // 日期
Row dataRow2 = sheet.createRow(2);
dataRow2.createCell(0).setCellValue(1002);
dataRow2.createCell(1).setCellValue("李四");
dataRow2.createCell(2).setCellValue("市场部");
dataRow2.createCell(3).setCellValue(new java.util.Date());
// 6. 自动调整列宽
for (int i = 0; i < 4; i++) {
sheet.autoSizeColumn(i);
}
// 7. 将 workbook 写入文件
try (FileOutputStream fileOut = new FileOutputStream("employees.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件 'employees.xlsx' 创建成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 8. 关闭 workbook,释放资源
try {
if (workbook != null) {
workbook.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建一个通用的创建单元格方法,可以应用样式
*/
private static void createCell(Row row, int column, String value, CellStyle style) {
Cell cell = row.createCell(column);
cell.setCellValue(value);
cell.setCellStyle(style);
}
/**
* 创建标题行样式
*/
private static CellStyle createHeaderStyle(Workbook workbook) {
CellStyle style = workbook.createCellStyle();
// 设置背景色
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 设置字体
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
// 设置边框
style.setBorderTop(BorderStyle.THIN);
style.setBorderBottom(BorderStyle.THIN);
style.setBorderLeft(BorderStyle.THIN);
style.setBorderRight(BorderStyle.THIN);
return style;
}
}
读取 Excel 文件
读取操作与写入相反,是从文件流中解析出 Workbook 对象,然后逐层遍历。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadExcelExample {
public static void main(String[] args) {
String filePath = "employees.xlsx";
try (FileInputStream fileIn = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fileIn)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 或 getSheet("员工信息")
// 2. 遍历所有行
for (Row row : sheet) {
// 跳过标题行 (如果需要)
if (row.getRowNum() == 0) {
continue;
}
// 3. 遍历所有单元格
for (Cell cell : row) {
// 根据单元格类型获取值
switch (cell.getCellType()) {
case STRING:
System.out.print(cell.getStringCellValue() + "\t");
break;
case NUMERIC:
// 检查是否是日期格式
if (DateUtil.isCellDateFormatted(cell)) {
System.out.print(cell.getDateCellValue() + "\t");
} else {
System.out.print(cell.getNumericCellValue() + "\t");
}
break;
case BOOLEAN:
System.out.print(cell.getBooleanCellValue() + "\t");
break;
case FORMULA:
System.out.print(cell.getCellFormula() + "\t");
break;
default:
System.out.print("UNKNOWN\t");
}
}
System.out.println(); // 换行
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改现有 Excel 文件
修改的关键在于使用 FileInputStream 读取现有文件,然后使用 FileOutputStream 写回同一个文件。注意: 这会直接覆盖原文件,操作前最好备份。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ModifyExcelExample {
public static void main(String[] args) {
String filePath = "employees.xlsx";
try (FileInputStream fileIn = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fileIn);
FileOutputStream fileOut = new FileOutputStream(filePath)) {
Sheet sheet = workbook.getSheet("员工信息");
// 假设我们要修改第二行第二列(姓名)的数据
Row rowToModify = sheet.getRow(1); // 行号从0开始,第二行是1
if (rowToModify != null) {
Cell cellToModify = rowToModify.getCell(1); // 列号从0开始,第二列是1
if (cellToModify == null) {
cellToModify = rowToModify.createCell(1);
}
cellToModify.setCellValue("王五");
System.out.println("已将第二行姓名修改为 '王五'");
}
// 写回文件
workbook.write(fileOut);
System.out.println("Excel 文件修改成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}
设置单元格样式
样式是通过 CellStyle 和 Font 对象来设置的,创建一次样式后,可以重复应用到多个单元格上,以提高效率。
// ... (在创建 Workbook 之后)
// 1. 创建一个样式对象
CellStyle style = workbook.createCellStyle();
// 2. 设置背景色
style.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
// 3. 设置字体
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
font.setColor(IndexedColors.WHITE.getIndex());
style.setFont(font);
// 4. 设置边框
style.setBorderBottom(BorderStyle.MEDIUM);
style.setBorderTop(BorderStyle.MEDIUM);
style.setBorderLeft(BorderStyle.MEDIUM);
style.setBorderRight(BorderStyle.MEDIUM);
// 5. 设置对齐方式
style.setAlignment(HorizontalAlignment.CENTER); // 水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER); // 垂直居中
// 6. 将样式应用到单元格
Cell cell = row.createCell(0);
cell.setCellValue("带样式的文本");
cell.setCellStyle(style);
进阶操作
处理日期
POI 不会自动将 Excel 中的数字识别为日期,你需要手动判断和转换。
// 写入日期
CreationHelper createHelper = workbook.getCreationHelper();
CellStyle dateStyle = workbook.createCellStyle();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-mm-dd"));
Cell dateCell = row.createCell(3);
dateCell.setCellValue(new java.util.Date());
dateCell.setCellStyle(dateStyle);
// 读取日期
if (DateUtil.isCellDateFormatted(cell)) {
java.util.Date date = cell.getDateCellValue();
System.out.println("这是一个日期: " + date);
} else {
// 可能是一个序列号,可以手动转换
double numericValue = cell.getNumericCellValue();
java.util.Date date = DateUtil.getJavaDate(numericValue);
System.out.println("这是一个日期序列号: " + date);
}
处理公式
// 设置公式
Cell formulaCell = row.createCell(4);
formulaCell.setCellFormula("SUM(A1:A2)"); // 假设 A1 和 A2 是数字
// 读取公式
if (cell.getCellType() == CellType.FORMULA) {
String formula = cell.getCellFormula();
System.out.println("单元格中的公式是: " + formula);
// 计算公式的结果
if (workbook instanceof FormulaEvaluator) {
FormulaEvaluator evaluator = ((Workbook) workbook).getCreationHelper().createFormulaEvaluator();
CellValue result = evaluator.evaluate(cell);
System.out.println("公式计算结果是: " + result.formatAsString());
}
}
合并单元格
// 合并从 (0,0) 到 (4,0) 的区域,即第1列的前5行
sheet.addMergedRegion(new CellRangeAddress(0, 4, 0, 0));
// 在合并区域的左上角单元格写入值
Cell mergedCell = sheet.getRow(0).getCell(0);
if (mergedCell == null) {
mergedCell = sheet.getRow(0).createCell(0);
}
mergedCell.setCellValue("合并后的单元格");
设置列宽和行高
// 设置第2列的宽度,宽度是字符宽度的256倍 sheet.setColumnWidth(1, 20 * 256); // 设置为20个字符宽 // 设置第3行的高度,高度是1/20个点 sheet.getRow(2).setHeight((short) (30 * 20)); // 设置为30个点高
性能优化 (SXSSF)
当处理大数据量(例如超过 10 万行)时,标准的 XSSFWorkbook 会将所有数据都加载到内存中,导致 OutOfMemoryError (内存溢出)。
解决方案是使用 SXSSFWorkbook (Streaming Usermodel API),它的工作原理是:
- 在内存中只保留一定数量的行(默认 100 行)。
- 当行数超过这个限制时,将最旧的行写入一个临时文件(
.tmp)。 - 将所有数据合并成一个
.xlsx文件。
示例:导出 50 万行数据
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import java.io.FileOutputStream;
import java.io.IOException;
public class BigDataExcelExample {
public static void main(String[] args) throws IOException {
// 1. 创建 SXSSFWorkbook,参数 100 表示在内存中保留100行
Workbook workbook = new SXSSFWorkbook(100);
Sheet sheet = workbook.createSheet("大数据报表");
// 2. 创建样式 (注意:SXSSF 对样式的支持有限,样式对象不能在行之间共享)
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setBold(true);
style.setFont(font);
// 3. 写入数据
for (int i = 0; i < 500000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("ID " + i);
row.createCell(1).setCellValue("用户 " + i);
if (i == 0) { // 只给第一行设置样式
row.getCell(0).setCellStyle(style);
row.getCell(1).setCellStyle(style);
}
// 模拟进度
if (i % 10000 == 0) {
System.out.println("已处理 " + i + " 行...");
}
}
// 4. 写入文件
try (FileOutputStream out = new FileOutputStream("big_data_report.xlsx")) {
workbook.write(out);
System.out.println("大数据Excel文件生成成功!");
} finally {
// 5. 清理临时文件 (非常重要!)
((SXSSFWorkbook) workbook).dispose();
if (workbook != null) {
workbook.close();
}
}
}
}
注意:
SXSSFWorkbook是只写的,不支持读取和修改。- 不能使用
Sheet.autoSizeColumn(),因为它需要遍历所有行,这在流式API中是不可能的,你需要预估列宽或手动设置。 - 样式对象不能在不同行之间共享,否则会抛出异常。
总结与最佳实践
-
选择正确的 API:
.xlsx文件:XSSFWorkbook.xls文件:HSSFWorkbook- 大数据量 (只写):
SXSSFWorkbook
-
资源管理: 使用
try-with-resources语句来管理Workbook和FileOutputStream/FileInputStream,确保文件流和对象被正确关闭。 -
样式复用: 对于
XSSFWorkbook,创建一个CellStyle或Font对象后,可以多次调用cell.setCellStyle(style)来应用到不同单元格,避免重复创建,提高性能。 -
数据类型: 写入数据时,使用
setCellValue()的正确重载方法(如setCellValue(String),setCellValue(double)),读取数据时,务必使用getCellType()判断类型,再调用对应的getXXXCellValue()方法。 -
异常处理: 文件操作和 IO 操作都可能抛出
IOException,务必进行捕获和处理。 -
大数据处理: 当你预见到数据量会很大时,直接使用
SXSSFWorkbook,不要等到内存溢出再重构。
希望这份详细的指南能帮助你掌握使用 Java 和 Apache POI 操作 Excel!
