POI和EasyExcel---处理Excel

发布于:2025-03-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

01 Apache POI简介

Apache POI 是一个开源的Java库,用于处理Microsoft Office格式的文档。它提供了丰富的API,允许开发者在Java应用中读取、写入和操作Excel、Word、PowerPoint等文件,是处理Office文档的常用工具。

核心功能

支持多种格式:包括经典的.xls(Excel 97-2003)、.xlsx(Excel 2007+)、.doc(Word 97-2003)、.docx(Word 2007+)、.ppt(PowerPoint)及Visio等。
跨平台性:作为纯Java库,可在任何支持Java的系统中使用。

主要组件

Excel处理
HSSF:操作.xls格式,适用于旧版Excel。
XSSF:处理.xlsx格式,支持Excel 2007+的新特性。
SXSSF:流式API,适合处理大数据量,避免内存溢出。
Word处理
HWPF:针对.doc格式,功能相对基础。
XWPF:支持.docx,提供更现代的API。
PowerPoint
HSLF:操作.ppt文件。
XSLF:处理.pptx格式。

使用场景

数据导出:将应用数据生成Excel报告。
数据导入:从上传的Excel中读取数据入库。
模板填充:用Word生成合同或账单等文档。
自动化测试:验证导出文件的内容正确性。

快速入门(以Excel为例)

Maven依赖

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>

示例:创建Excel并写入数据

  1. 创建Excel工作簿

    Workbook workbook = new XSSFWorkbook(); // 新建一个.xlsx格式的Excel文件
    
  2. 创建工作表

    Sheet sheet = workbook.createSheet("员工数据"); // 添加名为“员工数据”的工作表
    
  3. 构建标题行
    创建行对象:索引从0开始,第一行用于标题。

    Row headerRow = sheet.createRow(0); // 在第1行创建行
    

    填充单元格

    headerRow.createCell(0).setCellValue("姓名"); // A1单元格写入“姓名”
    headerRow.createCell(1).setCellValue("部门"); // B1单元格写入“部门”
    
  4. 添加数据行
    创建数据行:索引1对应Excel的第二行。

    Row dataRow = sheet.createRow(1); // 在第2行创建行
    

    填充数据

    dataRow.createCell(0).setCellValue("张三"); // A2写入“张三”
    dataRow.createCell(1).setCellValue("技术部"); // B2写入“技术部”
    
  5. 保存文件
    使用try-with-resources自动关闭流

    try (FileOutputStream fos = new FileOutputStream("output.xlsx")) {
        workbook.write(fos); // 将工作簿内容写入文件
    }
    

    手动关闭工作簿

    workbook.close(); // 释放资源
    
  6. 生成结果
    • 生成名为output.xlsx的Excel文件,包含“员工数据”工作表,数据如下:

    姓名 部门
    张三 技术部

注意事项
文件路径output.xlsx保存在程序运行的当前目录。
异常处理:示例中省略了详细异常处理,实际应用应捕获IOException等异常。
资源释放:虽然FileOutputStream自动关闭,但需手动关闭Workbook避免内存泄漏。
版本兼容性XSSFWorkbook适用于.xlsx格式;如需处理旧版.xls,改用HSSFWorkbook

扩展功能
样式设置:可通过CellStyle设置字体、颜色、边框等。
动态数据:结合数据库或循环结构添加多行数据。
读取现有文件:使用WorkbookFactory.create(new File("input.xlsx"))读取并修改现有文件。

优缺点分析

优势
• 功能全面,支持多种Office格式。
• 社区活跃,文档完善。
• 提供流式API处理大文件。

注意事项
• 内存消耗较大,处理大文件时建议使用SXSSF。
• 部分组件(如HWPF)对新格式支持有限。
• 日期和单元格格式需注意Excel的特定处理方式。

官网

Apache POI官网

替代方案

简单Excel操作:可考虑EasyExcel(阿里开源,内存优化)。
纯数据导出:CSV格式更轻量,无需复杂格式时推荐使用。

02 Excel基本读写操作

POI的主要方法

以下是 Apache POI 中常用核心方法的分类整理,适用于处理 Excel 文件(以 XSSF/HSSF 为例):


1. 工作簿(Workbook)操作

方法名 作用 示例代码
new XSSFWorkbook() 创建新的 .xlsx 工作簿 Workbook wb = new XSSFWorkbook();
new HSSFWorkbook() 创建新的 .xls 工作簿 Workbook wb = new HSSFWorkbook();
createSheet(String name) 创建 Sheet 页 Sheet sheet = wb.createSheet("数据");
getSheetAt(int index) 获取指定索引的 Sheet Sheet sheet = wb.getSheetAt(0);
getSheet(String name) 按名称获取 Sheet Sheet sheet = wb.getSheet("数据");
write(OutputStream stream) 将工作簿写入输出流 wb.write(new FileOutputStream("test.xlsx"));
close() 关闭工作簿释放资源 wb.close();

2. Sheet 页操作

方法名 作用 示例代码
createRow(int rownum) 创建行 Row row = sheet.createRow(0);
getRow(int rownum) 获取行 Row row = sheet.getRow(0);
getPhysicalNumberOfRows() 获取实际行数 int rowCount = sheet.getPhysicalNumberOfRows();
autoSizeColumn(int column) 自动调整列宽 sheet.autoSizeColumn(0);
addMergedRegion(CellRangeAddress) 合并单元格 sheet.addMergedRegion(new CellRangeAddress(0,0,0,2));

3. 行(Row)操作

方法名 作用 示例代码
createCell(int column) 创建单元格 Cell cell = row.createCell(0);
getCell(int column) 获取单元格 Cell cell = row.getCell(0);
getLastCellNum() 获取最后一个单元格索引+1 int lastCell = row.getLastCellNum();
setHeightInPoints(float height) 设置行高(单位:磅) row.setHeightInPoints(20);

4. 单元格(Cell)操作

方法名 作用 示例代码
setCellValue(String value) 设置字符串值 cell.setCellValue("Hello");
setCellValue(double value) 设置数字值 cell.setCellValue(100.5);
setCellValue(boolean value) 设置布尔值 cell.setCellValue(true);
setCellValue(Date value) 设置日期值 cell.setCellValue(new Date());
setCellFormula(String formula) 设置公式 cell.setCellFormula("SUM(A1:B1)");
getStringCellValue() 获取字符串值 String val = cell.getStringCellValue();
getNumericCellValue() 获取数字值 double num = cell.getNumericCellValue();
getBooleanCellValue() 获取布尔值 boolean flag = cell.getBooleanCellValue();
getDateCellValue() 获取日期值 Date date = cell.getDateCellValue();
getCellType() 获取单元格类型 CellType type = cell.getCellType();

5. 样式处理(CellStyle)

方法名 作用 示例代码
createCellStyle() 创建单元格样式 CellStyle style = wb.createCellStyle();
setFillForegroundColor(short) 设置背景颜色 style.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
setFillPattern(FillPatternType) 设置填充模式 style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
setBorderTop(BorderStyle) 设置上边框样式 style.setBorderTop(BorderStyle.THIN);
setFont(Font font) 设置字体样式 style.setFont(font);
setDataFormat(short format) 设置数据格式(如日期格式) style.setDataFormat(wb.createDataFormat().getFormat("yyyy-MM-dd"));

6. 高级数据操作

日期处理
// 判断单元格是否为日期格式
if (DateUtil.isCellDateFormatted(cell)) {
    Date date = cell.getDateCellValue();
}

// 创建日期格式样式
CellStyle dateStyle = wb.createCellStyle();
CreationHelper createHelper = wb.getCreationHelper();
dateStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
cell.setCellStyle(dateStyle);
公式计算
// 设置公式
cell.setCellFormula("A1+B1*0.1");

// 计算公式结果(需要触发计算)
FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator();
evaluator.evaluateFormulaCell(cell); // 计算结果会缓存到单元格

7. 流式写入(SXSSF 用于大数据量)

// 创建 SXSSFWorkbook(默认保留100行在内存中)
SXSSFWorkbook wb = new SXSSFWorkbook(100); 

// 写入数据后自动清理临时行
Sheet sheet = wb.createSheet();
for(int i=0; i<100000; i++){
    Row row = sheet.createRow(i);
    // ...写入数据
    if(i % 100 == 0) {
        ((SXSSFSheet)sheet).flushRows(100); // 手动刷新行
    }
}

// 必须显式清理临时文件
wb.dispose(); 

关键注意事项

  1. 资源释放:始终使用 try-with-resourcesfinally 块关闭资源

    try (Workbook wb = new XSSFWorkbook();
         FileOutputStream fos = new FileOutputStream("data.xlsx")) {
        // 操作代码
    }
    
  2. 类型安全:读取数据前必须检查单元格类型

    if (cell.getCellType() == CellType.NUMERIC) {
        // 处理数字
    }
    
  3. 性能优化
    • 避免频繁创建样式对象,应复用 CellStyle
    • 处理 10万+ 行数据时使用 SXSSFWorkbook
    • 禁用公式计算(若不需要):wb.setForceFormulaRecalculation(false);

03 EasyExcel和POI区别

EasyExcel 和 Apache POI 都是用于处理 Excel 文件的 Java 库,但它们在设计理念、性能优化和使用场景上有显著差异。以下是两者的详细对比:


核心区别对比表

对比维度 Apache POI EasyExcel
定位 通用 Office 文档处理(Excel/Word/PPT 等) 专注于 Excel 的高性能读写(阿里开源库)
内存管理 DOM 模式(全量加载)或 SAX 模式(流式读取) 默认流式读写,内存占用极低
API 复杂度 复杂(需手动处理行、列、样式) 高度封装,通过注解和监听器简化开发
大数据处理 需使用 SXSSFWorkbook 优化 原生支持百万级数据,无需额外配置
功能覆盖 支持 Excel 全部特性(公式、图表、宏等) 专注核心读写,复杂特性支持有限
学习成本 较高(需理解 Excel 底层结构) 较低(面向对象式开发)
社区生态 成熟(Apache 顶级项目,更新频繁) 活跃(阿里维护,文档完善但生态较新)

核心场景对比

1. 内存消耗与性能

POI
DOM 模式(XSSF/HSSF):全量加载文件到内存,处理 10万+ 行数据易导致 OOM。
SAX 模式(EventModel):流式读取可处理大文件,但需手动解析事件,开发复杂。
SXSSF:流式写入,适合生成大文件,但仍有内存管理负担。

EasyExcel
默认流式读写:逐行解析/写入,内存占用稳定(约几十MB处理百万行数据)。
自动内存优化:无需手动配置,开发者专注业务逻辑。

2. API 设计

POI

// 写入示例(POI)
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("数据");
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("POI 示例");
// 需要手动处理样式、资源关闭等

EasyExcel

// 写入示例(EasyExcel)
List<User> dataList = getData();
EasyExcel.write("output.xlsx", User.class)
         .sheet("数据")
         .doWrite(dataList); // 自动根据模型类生成 Excel
3. 功能覆盖

POI
• 支持 Excel 公式计算、条件格式、合并单元格、图表、宏等高级功能。
• 可读取/修改复杂模板(如带有公式和样式的报表)。

EasyExcel
• 专注核心读写,对公式、图表等复杂特性支持较弱。
• 适合纯数据导入导出场景,不适合需要保留复杂格式的模板操作。


选型建议

使用 POI 的场景

• 需要处理 Word/PPT 等非 Excel 文件
• 涉及 Excel 高级功能(如公式计算、动态图表)。
• 需要深度修改复杂模板(保留原有样式和结构)。
• 项目已深度依赖 POI,且无性能瓶颈。

使用 EasyExcel 的场景

大数据量读写(如百万行数据导出或解析)。
• 简单数据导入导出,无需复杂 Excel 特性
• 追求开发效率,希望用注解简化代码。
• 内存敏感型应用(如微服务环境下避免 OOM)。


性能对比示例

处理 100万行 x 10列 数据:

指标 POI (SXSSF) EasyExcel
内存占用 约 500MB(需调优) 稳定在 50MB 以内
写入时间 15-20 秒 8-12 秒
代码复杂度 高(需管理临时文件) 低(全自动管理)

集成与扩展

POI
• 可通过自定义 CellStyleFont 实现复杂样式。
• 支持与 JasperReports 等报表工具集成。

EasyExcel
• 提供 Spring Boot Starter,快速集成 Web 应用。
• 支持 监听器(Listener) 实现增量数据处理:
java // 流式读取示例 EasyExcel.read("input.xlsx", User.class, new UserDataListener()) .sheet() .doRead();


总结

EasyExcel大数据量 Excel 处理的终极方案,牺牲部分功能换取极致性能。
Apache POI全能型选手,适合需要精细控制 Excel 内容的场景。
• 两者可结合使用:用 EasyExcel 处理数据主体,POI 补充复杂格式调整。

04 EasyExcel 使用指南

一、基础准备

1. 添加依赖(Maven)
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.3</version>
</dependency>
2. 定义数据模型类
@Data // Lombok 注解自动生成 getter/setter
public class User {
    @ExcelProperty("姓名")      // 对应 Excel 列标题
    private String name;

    @ExcelProperty("年龄")
    private Integer age;

    @ExcelProperty(value = "入职日期", converter = LocalDateStringConverter.class)
    private LocalDate hireDate;
}

二、写入 Excel

1. 简单写入
// 准备数据
List<User> users = Arrays.asList(
    new User("张三", 28, LocalDate.now()),
    new User("李四", 32, LocalDate.now().minusYears(1))
);

// 写入文件
String fileName = "users.xlsx";
EasyExcel.write(fileName, User.class)
    .sheet("员工列表")       // Sheet 名称
    .doWrite(users);        // 写入数据
2. 自定义样式
EasyExcel.write(fileName, User.class)
    .registerWriteHandler(new AbstractColumnStyleStrategy() {
        @Override
        protected void setHeadCellStyle(Cell cell, Head head) {
            // 设置标题行样式
            CellStyle style = cell.getSheet().getWorkbook().createCellStyle();
            style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cell.setCellStyle(style);
        }
    })
    .sheet()
    .doWrite(users);

三、读取 Excel

1. 基础读取
// 定义监听器
public class UserListener extends AnalysisEventListener<User> {
    private List<User> cachedList = new ArrayList<>();

    // 每读取一行数据触发
    @Override
    public void invoke(User user, AnalysisContext context) {
        cachedList.add(user);
        if (cachedList.size() >= 100) {
            saveData(); // 批量处理
            cachedList.clear();
        }
    }

    // 读取完成后触发
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
    }

    private void saveData() {
        // 模拟入库操作
        System.out.println("保存数据: " + cachedList);
    }
}

// 执行读取
EasyExcel.read("users.xlsx", User.class, new UserListener())
    .sheet()
    .doRead();
2. 读取指定 Sheet
EasyExcel.read(fileName, User.class, new UserListener())
    .sheet(1)           // 读取第二个 Sheet(索引从0开始)
    // .sheet("Sheet名") // 或按名称读取
    .doRead();

四、高级功能

1. 自定义数据转换器
public class GenderConverter implements Converter<Integer> {
    @Override
    public Integer convertToJavaData(ReadCellData<?> cellData) {
        return "男".equals(cellData.getStringValue()) ? 1 : 0;
    }

    @Override
    public WriteCellData<?> convertToExcelData(Integer value) {
        return new WriteCellData<>(value == 1 ? "男" : "女");
    }
}

// 在模型类中使用
public class User {
    @ExcelProperty(value = "性别", converter = GenderConverter.class)
    private Integer gender;
}
2. 动态表头
// 动态生成表头
List<List<String>> head = new ArrayList<>();
head.add(Collections.singletonList("姓名"));
head.add(Collections.singletonList("年龄"));

// 动态写入数据
List<List<Object>> data = new ArrayList<>();
data.add(Arrays.asList("张三", 28));
data.add(Arrays.asList("李四", 32));

EasyExcel.write("dynamic.xlsx")
    .head(head)
    .sheet()
    .doWrite(data);
3. Web 导出(Spring Boot)
@GetMapping("/export")
public void export(HttpServletResponse response) throws IOException {
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setHeader("Content-Disposition", "attachment;filename=users.xlsx");
    
    List<User> users = userService.getAllUsers();
    EasyExcel.write(response.getOutputStream(), User.class)
        .sheet("用户数据")
        .doWrite(users);
}

五、性能优化

1. 分页查询写入(百万级数据)
// 分页查询参数
int pageSize = 1000;
int totalPage = 1000;

try (ExcelWriter excelWriter = EasyExcel.write("large_data.xlsx").build()) {
    for (int page = 1; page <= totalPage; page++) {
        // 模拟分页查询
        List<User> data = userService.getUsersByPage(page, pageSize);
        
        WriteSheet writeSheet = EasyExcel.writerSheet()
            .head(User.class)
            .build();
        
        excelWriter.write(data, writeSheet);
    }
}
2. 关闭自动合并(提升速度)
EasyExcel.write(fileName, User.class)
    .autoCloseStream(true)
    .autoTrim(true)         // 自动去除空格
    .ignoreEmptyRow(false)  // 不忽略空行
    .registerWriteHandler(new NoMergeStrategy()) // 禁止自动合并
    .sheet()
    .doWrite(data);

六、常见问题处理

1. 日期格式不一致
// 自定义日期转换器
public class CustomDateConverter implements Converter<LocalDate> {
    @Override
    public LocalDate convertToJavaData(ReadCellData<?> cellData) {
        return LocalDate.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy/MM/dd"));
    }

    @Override
    public WriteCellData<?> convertToExcelData(LocalDate value) {
        return new WriteCellData<>(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
    }
}
2. 处理空单元格
public class UserListener extends AnalysisEventListener<User> {
    @Override
    public void invoke(User user, AnalysisContext context) {
        if (user.getName() == null) {
            throw new ExcelAnalysisException("姓名不能为空");
        }
    }
}

通过以上方法,可以快速实现高性能的 Excel 读写操作。对于更复杂需求(如公式计算),仍需结合 Apache POI 或其它工具实现。EasyExcel 的详细文档可参考:官方文档

05 核心方法

以下是 EasyExcel 的核心方法分类整理,涵盖数据绑定、读写控制、样式处理等关键功能:


一、注解驱动(核心数据绑定)

1. @ExcelProperty
@ExcelProperty("姓名")                 // 绑定列标题
private String name;

@ExcelProperty(index = 2)             // 按列索引绑定(从0开始)
private Integer age;

@ExcelProperty(value = "日期", converter = CustomDateConverter.class)
private Date hireDate;                // 自定义数据转换器
2. @ExcelIgnore
@ExcelIgnore                         // 忽略该字段,不参与读写
private String secretKey;
3. @DateTimeFormat
@DateTimeFormat("yyyy年MM月dd日")     // 日期格式转换
private LocalDate birthday;

二、写入 Excel 核心方法

1. 基础写入
// 简单写入
EasyExcel.write("output.xlsx", User.class)
    .sheet("用户数据")
    .doWrite(userList);

// 追加写入(需使用 ExcelWriter)
try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {
    WriteSheet writeSheet = EasyExcel.writerSheet().build();
    excelWriter.write(dataList, writeSheet);
}
2. 表头控制
// 动态表头
List<List<String>> head = Arrays.asList(
    Arrays.asList("基本信息", "姓名"),  // 多级表头
    Arrays.asList("基本信息", "年龄")
);

EasyExcel.write(fileName)
    .head(head)
    .sheet()
    .doWrite(data);
3. 样式处理
// 注册样式策略
.write(fileName, User.class)
.registerWriteHandler(new AbstractColumnStyleStrategy() {
    @Override
    protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        // 设置内容单元格样式
        CellStyle style = cell.getSheet().getWorkbook().createCellStyle();
        style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
        cell.setCellStyle(style);
    }
})
.sheet()
.doWrite(data);

三、读取 Excel 核心方法

1. 监听器模式(流式读取)
// 定义监听器
public class DataListener extends AnalysisEventListener<User> {
    private List<User> cachedList = new ArrayList<>();

    @Override
    public void invoke(User user, AnalysisContext context) {
        cachedList.add(user);
        if (cachedList.size() >= 100) {
            processBatch(cachedList); // 批量处理
            cachedList.clear();
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        processBatch(cachedList);
    }
}

// 执行读取
EasyExcel.read("input.xlsx", User.class, new DataListener())
    .sheet()
    .doRead();
2. 读取控制
// 读取指定范围
.read(fileName, User.class, listener)
.headRowNumber(2)       // 跳过前两行(表头在第3行)
.sheet(0)               // 第一个Sheet
.doRead();

// 中断读取(在监听器中)
context.readSheetHolder().interrupt();

四、高级功能方法

1. 自定义转换器
public class StatusConverter implements Converter<Integer> {
    @Override
    public Integer convertToJavaData(ReadCellData<?> cellData) {
        return "启用".equals(cellData.getStringValue()) ? 1 : 0;
    }

    @Override
    public WriteCellData<?> convertToExcelData(Integer value) {
        return new WriteCellData<>(value == 1 ? "启用" : "禁用");
    }
}

// 在模型类中使用
@ExcelProperty(converter = StatusConverter.class)
private Integer status;
2. 动态模板写入
// 使用模板文件
String template = "template.xlsx";
EasyExcel.write(fileName, User.class)
    .withTemplate(template)
    .sheet()
    .doWrite(data);
3. Web 导出
// Spring Boot 响应导出
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
EasyExcel.write(response.getOutputStream(), User.class)
    .autoCloseStream(false)  // 不要自动关闭流
    .sheet()
    .doWrite(data);

五、性能优化方法

1. 分页查询写入
try (ExcelWriter excelWriter = EasyExcel.write("large.xlsx").build()) {
    for (int page = 1; ; page++) {
        List<User> data = userService.getPage(page, 1000);
        if (data.isEmpty()) break;
        
        WriteSheet sheet = EasyExcel.writerSheet()
            .head(User.class)
            .build();
        excelWriter.write(data, sheet);
    }
}
2. 禁用自动合并
.registerWriteHandler(new NoMergeStrategy()) // 提升写入速度

六、关键工具类

1. ExcelReaderBuilder
ExcelReaderBuilder readerBuilder = EasyExcel.read()
    .file(fileName)
    .head(User.class)
    .registerReadListener(listener);
2. ExcelWriterBuilder
ExcelWriterBuilder writerBuilder = EasyExcel.write()
    .file(fileName)
    .head(User.class)
    .autoCloseStream(true);

七、异常处理

1. 捕获解析异常
public class DataListener extends AnalysisEventListener<User> {
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        if (exception instanceof ExcelAnalysisException) {
            // 处理业务异常
        }
    }
}
2. 设置读取校验
.read(fileName, User.class, listener)
.extraRead(CoreConstants.SUPPORT_EXTRA_PROPERTY, "strict") // 严格模式
.doRead();

最佳实践建议

  1. 注解优先:尽量使用 @ExcelProperty 代替手动映射
  2. 批量处理:读取时每 100-1000 行处理一次数据
  3. 资源释放:使用 try-with-resources 管理 ExcelWriter
  4. 避免反射:大数据场景可关闭反射缓存
    WriteSheet writeSheet = EasyExcel.writerSheet()
        .useDefaultStyle(false)  // 关闭默认样式缓存
        .build();