核心工具:Apache POI 简介
Apache POI 是一个开源的 Java 库,由 Apache 软件基金会维护,它提供了 API,允许 Java 程序读取和写入 Microsoft Office 格式的文件,包括 Excel (.xls, .xlsx)、Word (.doc, .docx) 和 PowerPoint (.ppt, .pptx)。

POI 的主要模块:
| 模块 | 描述 | 适用场景 |
|---|---|---|
| POI-HSSF | 处理 Excel 97-2003 格式 (.xls) |
兼容旧版 Office,文件格式限制为 65536 行 x 256 列。 |
| POI-XSSF | 处理 Excel 2007+ 格式 (.xlsx) |
目前最主流的格式,基于 OOXML 标准,支持大数据量。 |
| POI-SXSSF | 流式 API,用于处理大数据量的 .xlsx 文件。 |
导出时性能极高,可以避免内存溢出,它会把不活跃的数据写入临时文件,而不是全部保存在内存中。注意:SXSSF 不支持读取。 |
| POI-OOXML | 一个通用模块,包含了 XSSF 和 SXSSF 的依赖。 | 在项目中引入这个模块即可,它会自动管理其他相关依赖。 |
推荐:在现代项目中,直接使用 poi-ooxml 是最方便的,它会自动引入处理 .xlsx 所需的依赖。
环境搭建(Maven 依赖)
在你的 pom.xml 文件中添加以下依赖,建议使用较新的稳定版本。
<dependencies>
<!-- Apache POI 核心依赖,用于处理 .xlsx 文件 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version> <!-- 请使用最新稳定版 -->
</dependency>
<!-- 如果需要处理 .xls 文件,需要额外引入 poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
Excel 导出
我们将分两种情况讲解导出:
- 基础导出:将数据写入一个简单的 Excel 文件。
- 大数据量导出:使用 SXSSF 避免内存溢出。
场景1:基础导出(.xlsx)
假设我们有一个用户列表,需要导出到 Excel。

准备数据模型
public class User {
private String name;
private int age;
private String email;
// 构造器、Getter 和 Setter
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
编写导出工具类
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
public class ExcelExporter {
public static void exportUsersToExcel(List<User> users, String filePath) throws IOException {
// 1. 创建一个新的 XSSFWorkbook 对象 (代表一个 .xlsx 文件)
Workbook workbook = new XSSFWorkbook();
// 2. 创建一个工作表
Sheet sheet = workbook.createSheet("用户列表");
// 3. 创建表头样式
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER); // 居中
// 4. 创建数据行样式
CellStyle dataStyle = workbook.createCellStyle();
dataStyle.setAlignment(HorizontalAlignment.CENTER);
// 5. 创建表头行
Row headerRow = sheet.createRow(0);
String[] headers = {"姓名", "年龄", "邮箱"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
// 6. 填充数据
int rowNum = 1;
for (User user : users) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(user.getName());
row.createCell(1).setCellValue(user.getAge());
row.createCell(2).setCellValue(user.getEmail());
// 为数据行应用样式
for(int i = 0; i < row.getLastCellNum(); i++) {
row.getCell(i).setCellStyle(dataStyle);
}
}
// 7. 自动调整列宽
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
// 8. 写入文件并关闭资源
try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
workbook.write(fileOut);
}
// 9. 关闭 Workbook
workbook.close();
System.out.println("Excel 文件已成功导出到: " + filePath);
}
public static void main(String[] args) {
// 模拟数据
List<User> users = List.of(
new User("张三", 28, "zhangsan@example.com"),
new User("李四", 32, "lisi@example.com"),
new User("王五", 25, "wangwu@example.com")
);
String exportPath = "C:/temp/users_export.xlsx";
try {
exportUsersToExcel(users, exportPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
场景2:大数据量导出(使用 SXSSF)
当导出数据量非常大(例如几十万行)时,使用 XSSFWorkbook 会导致所有数据都加载到内存中,极易造成 OutOfMemoryError。SXSSF (Streaming Usermodel) 是为此场景设计的。
核心区别:

XSSFWorkbook: 所有数据在内存中。SXSSFWorkbook: 在内存中只保留一部分活动行(默认为100行),其余的行会临时写入磁盘。
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class LargeDataExcelExporter {
public static void exportLargeDataToExcel(List<User> users, String filePath) throws IOException {
// 1. 创建一个 SXSSFWorkbook 对象
// 参数 100 表示在内存中保留的行数,超过的行会写入磁盘
Workbook workbook = new SXSSFWorkbook(100);
// 2. 后续步骤与 XSSFWorkbook 基本相同
Sheet sheet = workbook.createSheet("大数据用户列表");
// ... (创建样式、表头等代码与上面示例相同) ...
// 为了简洁,这里省略了样式创建代码,直接创建表头和数据
Row headerRow = sheet.createRow(0);
headerRow.createCell(0).setCellValue("姓名");
headerRow.createCell(1).setCellValue("年龄");
headerRow.createCell(2).setCellValue("邮箱");
int rowNum = 1;
for (User user : users) {
Row row = sheet.createRow(rowNum++);
row.createCell(0).setCellValue(user.getName());
row.createCell(1).setCellValue(user.getAge());
row.createCell(2).setCellValue(user.getEmail());
}
// 3. 写入文件
try (FileOutputStream fileOut = new FileOutputStream(filePath)) {
workbook.write(fileOut);
}
// 4. **非常重要**:清理由 SXSSF 创建的临时文件
((SXSSFWorkbook) workbook).dispose();
workbook.close();
System.out.println("大数据 Excel 文件已成功导出到: " + filePath);
}
public static void main(String[] args) {
// 模拟一个大数据量列表 (例如10万条)
List<User> largeUsers = new ArrayList<>();
for (int i = 0; i < 100_000; i++) {
largeUsers.add(new User("用户" + i, 20 + (i % 30), "user" + i + "@example.com"));
}
String exportPath = "C:/temp/large_users_export.xlsx";
try {
exportLargeDataToExcel(largeUsers, exportPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
Excel 导入
导入的核心是读取 Excel 文件,将每一行数据解析成我们的 Java 对象。
场景:从 .xlsx 文件导入用户数据
编写导入工具类
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class ExcelImporter {
public static List<User> importUsersFromExcel(String filePath) throws IOException {
List<User> users = new ArrayList<>();
// 1. 从文件输入流创建 Workbook 对象
try (FileInputStream fis = new FileInputStream(new File(filePath));
Workbook workbook = new XSSFWorkbook(fis)) {
// 2. 获取第一个工作表
Sheet sheet = workbook.getSheetAt(0); // 或者 workbook.getSheet("用户列表");
// 3. 从第二行开始遍历(跳过表头)
// sheet.getPhysicalNumberOfRows() 获取有数据的行数
for (int i = 1; i <= sheet.getPhysicalNumberOfRows(); i++) {
Row row = sheet.getRow(i);
if (row == null) {
continue; // 跳过空行
}
// 4. 读取单元格数据
// 注意:getCell(index) 可能返回 null,如果单元格为空
// 使用 getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK) 可以避免空指针
Cell nameCell = row.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
Cell ageCell = row.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
Cell emailCell = row.getCell(2, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
// 5. 处理不同类型的单元格数据
String name = getCellValueAsString(nameCell);
int age = (int) getCellValueAsNumeric(ageCell);
String email = getCellValueAsString(emailCell);
// 6. 创建 User 对象并添加到列表
users.add(new User(name, age, email));
}
}
return users;
}
/**
* 将单元格的值转换为字符串
*/
private static String getCellValueAsString(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 {
// 处理整数和浮点数
double numericValue = cell.getNumericCellValue();
// 如果是整数,则返回整数形式,否则返回原样
return (numericValue == (long) numericValue) ? String.valueOf((long) numericValue) : String.valueOf(numericValue);
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 如果是公式,则计算公式的值
return cell.getCellFormula();
default:
return "";
}
}
/**
* 将单元格的值转换为数字 (int)
*/
private static int getCellValueAsNumeric(Cell cell) {
if (cell == null) {
return 0;
}
switch (cell.getCellType()) {
case NUMERIC:
return (int) cell.getNumericCellValue();
case STRING:
try {
return Integer.parseInt(cell.getStringCellValue().trim());
} catch (NumberFormatException e) {
return 0; // 或者抛出异常
}
default:
return 0;
}
}
public static void main(String[] args) {
String importPath = "C:/temp/users_export.xlsx"; // 使用之前导出的文件
try {
List<User> importedUsers = importUsersFromExcel(importPath);
System.out.println("成功导入 " + importedUsers.size() + " 个用户:");
importedUsers.forEach(user ->
System.out.println("姓名: " + user.getName() + ", 年龄: " + user.getAge() + ", 邮箱: " + user.getEmail())
);
} catch (IOException e) {
e.printStackTrace();
}
}
}
高级技巧与最佳实践
-
样式与格式化
- 日期格式:使用
DataFormat来设置日期单元格的显示格式。CellStyle dateStyle = workbook.createCellStyle(); short dateFormat = workbook.createDataFormat().getFormat("yyyy-MM-dd"); dateStyle.setDataFormat(dateFormat); cell.setCellStyle(dateStyle); cell.setCellValue(new Date()); // 设置日期值 - 数字格式:同样可以使用
DataFormat来设置货币、百分比等格式。CellStyle currencyStyle = workbook.createCellStyle(); currencyStyle.setDataFormat(workbook.createDataFormat().getFormat("¥#,##0.00")); cell.setCellStyle(currencyStyle); cell.setCellValue(12345.67); - 边框和背景:通过
CellStyle的setBorder...和setFill...系列方法可以轻松设置单元格的边框和背景色。
- 日期格式:使用
-
合并单元格
// 合并从 (0,0) 到 (4,0) 的单元格 sheet.addMergedRegion(new CellRangeAddress(0, 4, 0, 0));
-
下拉列表
// 创建下拉列表数据 String[] listItems = {"选项1", "选项2", "选项3"}; DVConstraint constraint = DVConstraint.createExplicitListConstraint(listItems); // 设置下拉列表作用的区域 CellRangeAddressList addressList = new CellRangeAddressList(0, 10, 0, 0); // 前11行的第1列 // 创建数据有效性对象并应用到工作表 DataValidation validation = new HSSFDataValidation(addressList, constraint); sheet.addValidationData(validation); -
处理不同版本(.xls vs .xlsx) 可以通过文件后缀名来判断使用哪个
Workbook实现。Workbook workbook; if (filePath.endsWith(".xlsx")) { workbook = new XSSFWorkbook(new FileInputStream(file)); } else if (filePath.endsWith(".xls")) { workbook = new HSSFWorkbook(new FileInputStream(file)); } else { throw new IllegalArgumentException("不支持的文件类型"); }
其他优秀库推荐
除了 Apache POI,还有一些其他库在某些方面可能更优秀:
| 库 | 特点 | 适用场景 |
|---|---|---|
| EasyExcel | 阿里巴巴开源,基于 POI,但做了大量优化。最大特点是内存占用极低,读写的性能远超 POI,并且提供了非常简洁的 API(注解模式)。 | 强烈推荐,特别是对于 Web 应用中的导入导出,能显著降低服务器内存压力,代码也更简洁。 |
| JExcelApi | 轻量级,API 相对简单,功能没有 POI 强大。 | 对功能要求不高,追求轻量级的项目。 |
- 如果追求极致性能和低内存占用,尤其是在高并发的 Web 环境下,优先选择 EasyExcel。
- 如果项目已经广泛使用 POI,或者需要 POI 提供的所有高级功能(如复杂图表、VBA 等),继续使用 POI 是一个稳妥的选择。
希望这份详细的指南能帮助你掌握 Java 中 Excel 的导入导出!
