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

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

访客 技术 2026年7月28日 1

应用背景

在企业级开发中,经常需要处理 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 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

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

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

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...

发表评论

访客

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