
本文介绍一种纯内存方式递归解析多层嵌套 zip 文件(zip in zip in zip…)并精准提取指定 pdf 文件的可靠方案,避免 `stream closed` 异常与文件跳过问题。
在 Java 中处理“ZIP 文件内嵌 ZIP 文件再嵌 PDF”的场景时,常见误区是试图复用同一个 ZipInputStream 流来读取内外层 ZIP —— 这会导致 java.io.IOException: Stream Closed,因为 getNextEntry() 和 closeEntry() 会破坏流状态,且 ZipInputStream 不支持随机访问或流重置。
正确思路是:将内层 ZIP 条目完整读入内存字节数组,再基于该字节数组新建独立的 ZipInputStream 进行递归解析。这种方式完全绕过文件系统依赖,规避流生命周期冲突,也天然支持任意深度嵌套(如 ZIP → ZIP → ZIP → PDF)。
以下为可直接运行的核心实现:
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class NestedZipExtractor {
private static final String ZIP_EXTENSION = ".zip";
/**
* 从顶层 ZIP 文件中递归查找并提取指定名称的 PDF 文件(二进制数据)
* @param zipPath 顶层 ZIP 文件路径(如 "MegaData.zip")
* @param targetName 目标 PDF 文件名(如 "report.pdf"),需精确匹配路径名(含子目录)
* @return PDF 文件原始字节内容;未找到则返回 null
*/
public static byte[] extractPdfFromNestedZip(String zipPath, String targetName) throws IOException {
File source = new File(zipPath);
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(source)))) {
digInto(zis, targetName, result);
}
return result.size() > 0 ? result.toByteArray() : null;
}
/**
* 递归扫描 ZIP 流:匹配目标文件 or 提取内嵌 ZIP 并继续递归
*/
private static void digInto(ZipInputStream zis, String targetName, ByteArrayOutputStream output) throws IOException {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
String name = entry.getName();
// ✅ 成功匹配目标文件(注意:name 是 ZIP 内完整路径,如 "docs/2024/report.pdf")
if (name.equals(targetName) || name.equalsIgnoreCase(targetName)) {
copy(zis, output);
return;
}
// ? 发现内嵌 ZIP 文件,递归进入
if (name.toLowerCase().endsWith(ZIP_EXTENSION)) {
byte[] innerZipBytes = readEntryToByteArray(zis);
try (ZipInputStream innerZis = new ZipInputStream(new ByteArrayInputStream(innerZipBytes))) {
digInto(innerZis, targetName, output);
}
// 若已找到目标,递归调用会提前 return,此处无需额外检查
}
zis.closeEntry(); // 必须调用,否则后续 getNextEntry() 可能异常
}
}
/**
* 将当前 ZIP 条目内容完整读入内存字节数组
*/
private static byte[] readEntryToByteArray(ZipInputStream zis) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
copy(zis, buffer);
return buffer.toByteArray();
}
/**
* 通用流拷贝工具(适用于二进制数据)
*/
private static void copy(InputStream from, OutputStream to) throws IOException {
byte[] buf = new byte[8192];
int len;
while ((len = from.read(buf)) != -1) {
to.write(buf, 0, len);
}
to.flush();
}
// ✅ 使用示例
public static void main(String[] args) {
try {
// 提取嵌套 ZIP 中路径为 "data/invoice.pdf" 的 PDF
byte[] pdfBytes = extractPdfFromNestedZip("C:\\Scan\\Data\\archive.zip", "data/invoice.pdf");
if (pdfBytes != null) {
// 保存到磁盘
Files.write(Paths.get("extracted_invoice.pdf"), pdfBytes);
System.out.println("✅ PDF extracted successfully: " + pdfBytes.length + " bytes");
} else {
System.err.println("❌ Target PDF not found in nested ZIP structure.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}? 关键注意事项:
- 路径匹配要精确:entry.getName() 返回的是 ZIP 内部完整路径(如 "subfolder/archive.zip" 或 "docs/report.pdf"),请确保 targetName 与之完全一致(区分大小写建议用 equalsIgnoreCase)。
- 内存安全:该方案将每个内嵌 ZIP 条目加载至内存,适合中小型嵌套 ZIP(单个 ZIP
- 资源自动管理:所有 ZipInputStream 均通过 try-with-resources 确保关闭,避免句柄泄漏。
- 不依赖文件系统:全程基于 InputStream/ByteArrayInputStream,无需创建中间解压目录,线程安全且便于集成进 Web 或批处理服务。
通过此方法,你不仅能稳定提取深层嵌套 ZIP 中的 PDF,还可轻松扩展为查找多种类型文件(如 .xlsx, .json)、收集全部 PDF 列表、或校验嵌套层级——核心逻辑清晰、健壮、可维护。










