当前位置:首页 > 技术 > 正文内容

Java 基于 Apache POI 提取 Excel 单元格内图片的技术实现

访客 技术 2026年7月28日 15

应用背景

在企业级开发中,经常需要处理 Excel 数据的导入,其中一种复杂场景是读取 Excel 文件中嵌入的图片并将其关联到对应的业务数据行。由于图片在 Excel 中通常作为浮动对象存储,而非直接包含在单元格的值中,因此需要利用 Apache POI 提供的底层绘图对象来定位并提取这些图片。

Excel图片导入示例

环境配置

首先,在项目的 pom.xml 中引入 Apache POI 的核心依赖。建议使用 4.x 或更高版本以获得更好的 OOXML 支持。

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

临时存储目录生成

在处理文件上传时,通常需要一个临时目录来缓冲提取出来的图片流,处理完成后再统一迁移至文件服务器或云存储。

/**
 * 创建基于任务ID的临时存储路径
 * @param taskId 导入任务唯一标识
 * @return 临时目录路径
 */
public static String acquireWorkPath(String taskId) {
    String dateLabel = new SimpleDateFormat("yyyyMMdd").format(new Date());
    String subPath = String.format("temp/%s_%s", dateLabel, taskId);
    File dir = new File(subPath);
    if (!dir.exists()) {
        dir.mkdirs();
    }
    return subPath;
}

解析 Excel 中的图片资源

实现思路是遍历 Sheet 中的绘图层(Drawing),获取所有的形状(Shape),识别出图片类型并根据其锚点(Anchor)信息确定图片所在的行索引。

/**
 * 提取指定 Sheet 中的图片并返回行号映射关系
 * @param targetSheet 目标工作表
 * @param jobToken 任务令牌
 * @return Map<行索引, 图片文件名列表>
 */
public static Map<Integer, List<String>> fetchImagesFromSheet(Sheet targetSheet, String jobToken) throws Exception {
    if (jobToken == null || jobToken.isEmpty()) {
        return Collections.emptyMap();
    }

    Map<Integer, List<String>> rowImageMap = new HashMap<>();
    String storagePath = acquireWorkPath(jobToken);
    Workbook wb = targetSheet.getWorkbook();
    
    // 获取或创建绘图管理器
    Drawing<?> drawing = targetSheet.createDrawingPatriarch();
    
    if (wb instanceof XSSFWorkbook) {
        processXSSFImages((XSSFDrawing) drawing, storagePath, jobToken, rowImageMap);
    } else if (wb instanceof HSSFWorkbook) {
        processHSSFImages((HSSFPatriarch) drawing, storagePath, jobToken, rowImageMap);
    }

    return rowImageMap;
}

private static void processXSSFImages(XSSFDrawing drawing, String path, String token, Map<Integer, List<String>> map) throws IOException {
    int seq = 0;
    for (XSSFShape shape : drawing.getShapes()) {
        if (shape instanceof XSSFPicture) {
            XSSFPicture pic = (XSSFPicture) shape;
            savePictureData(pic, path, token, ++seq, map);
        }
    }
}

private static void processHSSFImages(HSSFPatriarch drawing, String path, String token, Map<Integer, List<String>> map) throws IOException {
    int seq = 0;
    for (HSSFShape shape : drawing.getChildren()) {
        if (shape instanceof HSSFPicture) {
            HSSFPicture pic = (HSSFPicture) shape;
            savePictureData(pic, path, token, ++seq, map);
        }
    }
}

图片持久化与坐标匹配

在提取图片时,ClientAnchor 是关键对象。它记录了图片左上角和右下角所在的行列坐标。通常我们以起始行(Row1)作为该图片所属的数据行。

private static void savePictureData(Picture pic, String dir, String token, int index, Map<Integer, List<String>> map) throws IOException {
    PictureData data = pic.getPictureData();
    ClientAnchor anchor = pic.getClientAnchor();
    
    // 获取图片定位行
    int targetRow = anchor.getRow1();
    String extension = data.suggestFileExtension();
    
    // 构造唯一文件名
    String fileName = String.format("img_%s_R%d_%d.%s", token, targetRow, index, extension);
    File targetFile = new File(dir, fileName);
    
    // 写入文件系统
    try (FileOutputStream fos = new FileOutputStream(targetFile)) {
        fos.write(data.getData());
    }
    
    // 更新结果集
    map.computeIfAbsent(targetRow, k -> new ArrayList<>()).add(fileName);
}

技术要点与优化建议

处理 Excel 图片时,内存管理至关重要。尤其是 .xlsx 格式的大型文件,图片数据会显著增加 JVM 堆内存占用。

  • 内存释放: pictureData.getData() 会在内存中创建字节数组副本。在完成 IO 写入后,应确保引用被及时置空,以便 GC 回收。
  • 异常处理: 对于非图片格式(如 Excel 中嵌入的图标或图表),suggestFileExtension 可能会返回非预期结果。建议增加文件头校验或扩展名过滤。
  • 大文件流式处理: 如果 Excel 文件极大且包含数千张图片,建议考虑使用 SXSSF 模式的变种处理逻辑,或者通过解压 .xlsx (实质是 XML 压缩包) 直接读取 xl/media 目录下的原始文件,以降低内存压力。
  • 清理机制: 任务结束后,务必通过 File.delete() 或文件清理工具类移除临时文件夹,防止磁盘空间溢出。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

linux screen 用法详情 (nohup 的替代方案)

一、screen 是什么?能干嘛?screen 是一个终端复用器,可以:在一个 SSH 会话中开多个“虚拟终端”SSH 断线后,程序仍然在后台运行随时重新连接到原来的会话特别适合:nohup 的替代方案跑脚本 / 爬虫 / 训练模型运维、远程开发二、安装 screen# CentOS / Rocky / Almayum install -y screen# Debian / Ubuntuapt i...

PHPStan 有什么用?怎么用?

PHPStan 是一个 PHP 的静态分析工具,在不运行代码的情况下就能帮你发现潜在问题,比如:传错类型(把 string 传给接受 int 的函数)访问不存在的属性 / 方法null 没处理好永远不会执行到的代码数组 key/值类型不一致返回值不符合声明注释和真实类型不匹配它非常适合:想提升代码质量、减少线上 bug、统一团队风格的人(尤其是中大型项目)。一、PHPStan 有什么用(通俗点说)...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。