- 环境准备:如何添加 POI 依赖。
- 核心概念:
Workbook,Sheet,Row,Cell的关系。 - 详细操作:从创建、读取、修改到写入 Excel 文件的完整流程。
- 不同版本的区别:
.xls(HSSF) 和.xlsx(XSSF) 的处理方式。 - 实用技巧:样式、日期处理、大文件优化等。
环境准备 (Maven)
如果你使用 Maven,在 pom.xml 文件中添加 POI 的依赖,根据你处理的 Excel 版本选择不同的依赖。
处理 .xlsx (Office 2007+) 格式 (推荐)
这是目前最主流的格式,推荐使用。
<dependencies>
<!-- POI 核心库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version> <!-- 请使用最新版本 -->
</dependency>
<!-- POI 对 OOXML (Office Open XML) 格式的支持 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
<!-- 用于处理 XML 的依赖,通常包含在 poi-ooxml 中,但显式声明更清晰 -->
<dependency>
<groupId>org.apache.xmlbeans</groupId>
<artifactId>xmlbeans</artifactId>
<version>5.1.1</version>
</dependency>
<!-- 用于解压 OOXML 包中文件的依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-lite</artifactId>
<version>5.2.3</version>
</dependency>
</dependencies>
处理 .xls (Office 97-2003) 格式 (旧版)
如果你的项目需要兼容旧版 Excel,需要额外添加 poi-scratchpad 依赖。
<!-- 如果需要处理 .xls 格式,添加此依赖 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.3</version>
</dependency>
核心概念
理解 POI 操作 Excel 的核心对象模型至关重要,它们之间是层层包含的关系:
Workbook(工作簿):代表一个 Excel 文件,它是所有表格的顶级容器。- 对于
.xlsx文件,使用XSSFWorkbook。 - 对于
.xls文件,使用HSSFWorkbook。
- 对于
Sheet(工作表):代表Workbook中的一个工作表,"Sheet1"。Row(行):代表Sheet中的某一行,行号从0开始。Cell(单元格):代表Row中的一个单元格,列号也从0开始。
关系图:
Workbook -> Sheet -> Row -> Cell
详细操作示例
下面我们通过代码示例来演示最常见的操作。
示例1:创建并写入一个 .xlsx 文件
这个例子将创建一个新的 Excel 文件,写入一些数据,并设置单元格样式。
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. 创建样式 (可选)
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerFont.setFontHeightInPoints((short) 14);
headerFont.setColor(IndexedColors.RED.getIndex());
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
// 4. 创建表头行 (第 0 行)
Row headerRow = sheet.createRow(0);
String[] headers = {"ID", "姓名", "年龄", "入职日期", "薪资"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 5. 创建数据行
// 第二行数据
Row dataRow1 = sheet.createRow(1);
dataRow1.createCell(0).setCellValue(1);
dataRow1.createCell(1).setCellValue("张三");
dataRow1.createCell(2).setCellValue(28);
dataRow1.createCell(3).setCellValue(new java.util.Date()); // 日期类型
dataRow1.createCell(4).setCellValue(8500.50);
// 第三行数据
Row dataRow2 = sheet.createRow(2);
dataRow2.createCell(0).setCellValue(2);
dataRow2.createCell(1).setCellValue("李四");
dataRow2.createCell(2).setCellValue(32);
dataRow2.createCell(3).setCellValue(new java.util.Date());
dataRow2.createCell(4).setCellValue(12000.00);
// 6. 自动调整列宽以适应内容
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 7. 将工作簿写入文件
try (FileOutputStream fileOut = new FileOutputStream("D:/temp/员工信息.xlsx")) {
workbook.write(fileOut);
System.out.println("Excel 文件创建成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
// 8. 关闭工作簿,释放资源
try {
workbook.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
示例2:读取一个 .xlsx 文件
这个例子演示如何读取已有的 Excel 文件内容。
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 = "D:/temp/员工信息.xlsx";
try (FileInputStream fileIn = new FileInputStream(filePath);
Workbook workbook = new XSSFWorkbook(fileIn)) {
// 1. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 或者 workbook.getSheet("员工信息");
// 2. 遍历每一行 (从第一行开始,跳过表头)
for (int rowNum = 1; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null) {
continue; // 跳过空行
}
// 3. 遍历每个单元格
Cell idCell = row.getCell(0);
Cell nameCell = row.getCell(1);
Cell ageCell = row.getCell(2);
Cell dateCell = row.getCell(3);
Cell salaryCell = row.getCell(4);
// 4. 获取单元格值,并处理不同类型
int id = (int) idCell.getNumericCellValue();
String name = nameCell.getStringCellValue();
int age = (int) ageCell.getNumericCellValue();
// 日期处理
DataFormatter dataFormatter = new DataFormatter(); // 可以直接获取单元格显示的字符串值
String dateStr = dataFormatter.formatCellValue(dateCell);
java.util.Date date = dateCell.getDateCellValue();
double salary = salaryCell.getNumericCellValue();
System.out.printf("ID: %d, 姓名: %s, 年龄: %d, 入职日期: %s / %s, 薪资: %.2f%n",
id, name, age, dateStr, date, salary);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:读取单元格时,最好先判断单元格类型 (cell.getCellType()),或者使用 DataFormatter 来安全地获取字符串形式的值,避免因类型不匹配而出错。
示例3:修改一个已有的 .xlsx 文件
POI 不能直接修改文件,它的工作流程是:读取 -> 修改 -> 写入新文件。
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 inputFilePath = "D:/temp/员工信息.xlsx";
String outputFilePath = "D:/temp/员工信息_修改后.xlsx";
try (FileInputStream fileIn = new FileInputStream(inputFilePath);
Workbook workbook = new XSSFWorkbook(fileIn);
FileOutputStream fileOut = new FileOutputStream(outputFilePath)) {
// 1. 获取工作表
Sheet sheet = workbook.getSheet("员工信息");
// 2. 修改第二行(索引为1)的数据
Row rowToModify = sheet.getRow(1);
if (rowToModify != null) {
// 修改姓名
Cell nameCell = rowToModify.getCell(1);
nameCell.setCellValue("张三(已更新)");
// 修改薪资
Cell salaryCell = rowToModify.getCell(4);
salaryCell.setCellValue(9000.00);
}
// 3. 在文件末尾添加新一行
int lastRowNum = sheet.getLastRowNum();
Row newRow = sheet.createRow(lastRowNum + 1);
newRow.createCell(0).setCellValue(3);
newRow.createCell(1).setCellValue("王五");
newRow.createCell(2).setCellValue(25);
newRow.createCell(3).setCellValue(new java.util.Date());
newRow.createCell(4).setCellValue(7500.0);
// 4. 将修改后的内容写入新文件
workbook.write(fileOut);
System.out.println("Excel 文件修改成功,已保存为: " + outputFilePath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
.xls vs .xlsx 的区别
| 特性 | .xls (HSSF) |
.xlsx (XSSF) |
|---|---|---|
| 文件格式 | 二进制格式,基于 OLE 复合文档。 | Office Open XML (OOXML) 格式,本质上是 ZIP 压缩包。 |
| 最大行数 | 65,536 行 (2^16) | 1,048,576 行 (2^20) |
| 最大列数 | 256 列 (2^8) | 16,384 列 (2^14) |
| 内存占用 | 相对较低,因为是纯内存操作。 | 相对较高,特别是处理大文件时。 |
| 处理方式 | HSSFWorkbook |
XSSFWorkbook |
| 大文件支持 | 不支持,内存易溢出。 | 支持,有 SXSSF (Streaming Usermodel) 模式。 |
实用技巧
1 处理大文件 (SXSSF)
当处理超过 10,000 行数据时,使用 XSSFWorkbook 会导致内存占用过高,甚至 OutOfMemoryError,此时应使用 SXSSFWorkbook (Streaming Usermodel)。
特点:
- 它会将不活跃的行(超出滑动窗口大小的行)写入临时文件,从而保持内存占用在一个较低的水平。
- 它是
XSSFWorkbook的一个子集,不支持所有功能(如单元格样式、公式计算等)。
示例:
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 LargeExcelExample {
public static void main(String[] args) throws IOException {
// 1. 创建 SXSSFWorkbook,参数是滑动窗口大小 (100)
Workbook workbook = new SXSSFWorkbook(100);
Sheet sheet = workbook.createSheet("大数据");
// 2. 写入 100,000 行数据
for (int i = 0; i < 100000; i++) {
Row row = sheet.createRow(i);
row.createCell(0).setCellValue("ID " + i);
row.createCell(1).setCellValue("Name " + i);
}
// 3. 写入文件
try (FileOutputStream out = new FileOutputStream("D:/temp/大数据.xlsx")) {
workbook.write(out);
} finally {
// 4. 清理临时文件 (非常重要!)
((SXSSFWorkbook) workbook).dispose();
workbook.close();
}
System.out.println("大文件生成完成!");
}
}
2 日期处理
Excel 有自己的日期系统,直接用 java.util.Date 写入,Excel 可能会将其识别为数字,需要设置单元格类型为 CellType.NUMERIC 并应用日期格式。
CellStyle dateStyle = workbook.createCellStyle();
CreationHelper createHelper = workbook.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
Cell dateCell = row.createCell(3);
dateCell.setCellValue(new java.util.Date()); // 写入日期
dateCell.setCellStyle(dateStyle); // 应用日期格式
3 单元格类型判断
在读取时,务必判断单元格类型,否则容易出错。
Cell cell = row.getCell(0);
switch (cell.getCellType()) {
case STRING:
System.out.println(cell.getStringCellValue());
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
System.out.println(cell.getDateCellValue());
} else {
System.out.println(cell.getNumericCellValue());
}
break;
case BOOLEAN:
System.out.println(cell.getBooleanCellValue());
break;
case FORMULA:
System.out.println(cell.getCellFormula());
break;
default:
System.out.println("未知类型");
}
POI 是一个非常强大且灵活的 Java Excel 操作库,掌握其核心的 Workbook -> Sheet -> Row -> Cell 模型,并根据 .xls 和 .xlsx 的区别选择合适的实现类,你就可以应对绝大多数 Excel 文件的读写和修改任务,对于大文件场景,请务必使用 SXSSFWorkbook 来优化内存。
