使用Apache POI读取Excel中的图片:完整指南

使用Apache POI读取Excel中的图片:完整指南

在处理Excel文件时,我们经常需要提取其中的图片,比如报表中的图表、产品图片等。Apache POI是一个强大的Java库,能够读取和写入Microsoft Office文档。本文将详细介绍如何利用POI读取Excel中的图片,无论老旧的.xls(HSSF)还是新版的.xlsx(XSSF)格式。

前置知识

确保你的项目中已添加POI依赖。Maven示例:

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

基本原理

Excel中的图片并不直接嵌入单元格,而是作为独立的对象存在于工作簿的某个位置上,并与单元格通过锚点(Anchor)关联。POI提供了HSSFPatriarch(HSSF)和XSSFDrawing(XSSF)来管理这些图形对象。读取图片的步骤通常包括:

  1. 打开工作簿
  2. 获取指定的工作表
  3. 获取工作表的绘图对象
  4. 遍历绘图对象中的所有图形,判断是否为图片
  5. 提取图片数据并保存

读取HSSF(.xls)中的图片

使用HSSFWorkbookHSSFSheet。示例代码:

import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import java.io.*;
import java.util.List;

public class ReadHSSFImages {
    public static void main(String[] args) throws IOException {
        FileInputStream file = new FileInputStream("old_format.xls");
        HSSFWorkbook workbook = new HSSFWorkbook(file);
        HSSFSheet sheet = workbook.getSheetAt(0);
        HSSFPatriarch patriarch = sheet.getDrawingPatriarch();
        if (patriarch != null) {
            List shapes = patriarch.getChildren();
            for (HSSFShape shape : shapes) {
                if (shape instanceof HSSFPicture) {
                    HSSFPicture picture = (HSSFPicture) shape;
                    HSSFPictureData picData = picture.getPictureData();
                    byte[] data = picData.getData();
                    String ext = picData.suggestFileExtension();
                    // 保存图片
                    FileOutputStream out = new FileOutputStream("image." + ext);
                    out.write(data);
                    out.close();
                }
            }
        }
        workbook.close();
        file.close();
    }
}

读取XSSF(.xlsx)中的图片

使用XSSFWorkbookXSSFSheet。注意getDrawingPatriarch返回的是XSSFDrawing。示例代码:

import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import java.io.*;
import java.util.List;

public class ReadXSSFImages {
    public static void main(String[] args) throws IOException {
        FileInputStream file = new FileInputStream("new_format.xlsx");
        XSSFWorkbook workbook = new XSSFWorkbook(file);
        XSSFSheet sheet = workbook.getSheetAt(0);
        XSSFDrawing drawing = sheet.getDrawingPatriarch();
        if (drawing != null) {
            List shapes = drawing.getShapes();
            for (XSSFShape shape : shapes) {
                if (shape instanceof XSSFPicture) {
                    XSSFPicture picture = (XSSFPicture) shape;
                    XSSFPictureData picData = picture.getPictureData();
                    byte[] data = picData.getData();
                    String ext = picData.suggestFileExtension();
                    FileOutputStream out = new FileOutputStream("image." + ext);
                    out.write(data);
                    out.close();
                }
            }
        }
        workbook.close();
        file.close();
    }
}

通用方法:统一处理两种格式

由于POI提供了统一的接口(如Picture, PictureData),我们可以编写一个通用的方法,根据工作簿类型分别处理:

import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import java.io.*;
import java.util.List;

public class ReadExcelImages {
    public static void main(String[] args) throws IOException {
        String filePath = "example.xlsx"; // 或 .xls
        Workbook workbook = WorkbookFactory.create(new FileInputStream(filePath));
        Sheet sheet = workbook.getSheetAt(0);
        Drawing drawing = sheet.getDrawingPatriarch();
        if (drawing != null) {
            for (Shape shape : drawing) {
                if (shape instanceof Picture) {
                    Picture picture = (Picture) shape;
                    PictureData picData = picture.getPictureData();
                    byte[] data = picData.getData();
                    String ext = picData.suggestFileExtension();
                    FileOutputStream out = new FileOutputStream("image_" + System.currentTimeMillis() + "." + ext);
                    out.write(data);
                    out.close();
                }
            }
        }
        workbook.close();
    }
}

常见问题与注意事项

  • 没有图片被读取:检查Excel中的图片是否确实是“图片”对象,而非OLE对象或图表。某些图表可能以不同方式存储。
  • 锚点偏移:若需要获取图片在单元格中的位置,可以使用picture.getClientAnchor(),但坐标相对复杂,通常用于更精确的映射。
  • 性能考虑:对于大型Excel文件,频繁的I/O和内存操作可能影响性能。可考虑使用流式API如SXSSFWorkbook(仅限写入)或分批处理。
  • 图片格式suggestFileExtension()返回的扩展名可能不完全准确,例如EMF、WMF等,但常见格式(PNG、JPEG)通常正确。

扩展应用

除了提取图片,你还可以:

  • 替换图片:通过picture.setPictureData(newPicData)修改。
  • 获取图片与单元格间的相对位置,实现数据与图片的关联。
  • 结合其他POI功能读取图片的元数据,如文件名、描述等。

总结

使用Apache POI读取Excel中的图片并不复杂,关键在于正确获取绘图对象并遍历其中的图片类形状。通过本文提供的代码,你可以轻松地将Excel中的图片提取出来,存入本地或进行后续处理。记得根据你的具体需求调整异常处理和路径管理。

如果你遇到了其他问题,欢迎在评论区留言讨论。