ZIP读取器-ZipReader
# 📚简介
ZipReader类是Hutool压缩模块中用于读取Zip文件或流的工具类,主要用于Zip文件的解压操作。它封装了JDK的ZipFile和ZipInputStream,提供了更简洁易用的API,支持从文件或流中读取Zip内容,并提供了ZipBomb防护机制。
# 🏗️核心结构
# 1. 类定义
public class ZipReader implements Closeable {
private static final int DEFAULT_MAX_SIZE_DIFF = 100;
private final ZipResource resource;
private int maxSizeDiff = DEFAULT_MAX_SIZE_DIFF;
// ...
}
# 2. 核心属性
| 属性名 | 类型 | 描述 |
|---|---|---|
resource | ZipResource | Zip资源,封装了Zip文件或流的访问 |
maxSizeDiff | int | 检查ZipBomb文件差异倍数,默认100,-1表示不检查 |
DEFAULT_MAX_SIZE_DIFF | int | 默认的ZipBomb检查差异倍数,值为100 |
# 🔧 API说明
# 1. 创建ZipReader实例
# of(File zipFile, Charset charset)
从Zip文件创建ZipReader实例。
参数说明:
zipFile:要读取的Zip文件charset:编码方式,用于处理文件名和内容的编码
返回值:
- ZipReader实例,用于链式调用
# of(InputStream in, Charset charset)
从输入流创建ZipReader实例。
参数说明:
in:Zip输入流,一般为文件输入流charset:编码方式,用于处理文件名和内容的编码
返回值:
- ZipReader实例,用于链式调用
# 2. 设置ZipBomb防护
# setMaxSizeDiff(int maxSizeDiff)
设置检查ZipBomb文件差异倍数,用于防止Zip炸弹攻击。
参数说明:
maxSizeDiff:差异倍数,-1表示不检查ZipBomb,默认100
返回值:
- ZipReader实例,用于链式调用
说明:
- ZipBomb是一种恶意压缩文件,解压后体积会急剧膨胀,可能导致系统资源耗尽
- 设置合理的
maxSizeDiff可以有效防止ZipBomb攻击 - 差异倍数 = 解压后大小 / 压缩前大小
# 3. 获取文件流
# get(String path)
获取Zip文件中指定路径的文件流。
参数说明:
path:文件在Zip中的路径,相对于Zip根目录
返回值:
- 文件输入流,可以读取文件内容
实现逻辑:
- 如果是文件模式,直接获取Entry对应的流
- 如果是流模式,遍历entry后,找到对应流返回
# 4. 解压操作
# readTo(File outFile)
将Zip文件解压到指定目录。
参数说明:
outFile:解压到的目标目录
返回值:
- 解压的目录,即参数中的
outFile
异常:
IORuntimeException:IO异常
# readTo(File outFile, Predicate<ZipEntry> entryFilter)
带过滤器的解压,只解压符合条件的文件。
参数说明:
outFile:解压到的目标目录entryFilter:过滤器,只保留test()方法返回true的文件
返回值:
- 解压的目录,即参数中的
outFile
异常:
IORuntimeException:IO异常
# 5. 遍历Zip条目
# read(Consumer<ZipEntry> consumer)
读取并处理Zip文件中的每一个ZipEntry。
参数说明:
consumer:ZipEntry处理器,用于处理每个Zip条目
返回值:
- ZipReader实例,用于链式调用
异常:
IORuntimeException:IO异常
# 6. 资源关闭
# close()
关闭ZipReader,释放资源。
异常:
IORuntimeException:IO异常
# 🚀 使用示例
# 1. 从文件创建ZipReader并解压
// 从文件创建ZipReader
ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8);
try {
// 解压到指定目录
File destDir = FileUtil.file("output");
reader.readTo(destDir);
Console.log("解压完成,解压到目录:{}", destDir.getAbsolutePath());
} finally {
// 关闭资源
reader.close();
}
# 2. 使用try-with-resources自动关闭
// 使用try-with-resources自动关闭资源
try (ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8)) {
reader.readTo(FileUtil.file("output"));
Console.log("解压完成!");
}
# 3. 从输入流创建ZipReader
// 从输入流创建ZipReader
try (InputStream in = FileUtil.getInputStream("test.zip");
ZipReader reader = ZipReader.of(in, CharsetUtil.CHARSET_UTF_8)) {
reader.readTo(FileUtil.file("output"));
Console.log("从流解压完成!");
}
# 4. 使用过滤器解压特定文件
// 只解压txt文件
try (ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8)) {
reader.readTo(FileUtil.file("output"), entry -> {
// 只解压txt文件,排除目录
return !entry.isDirectory() && entry.getName().endsWith(".txt");
});
Console.log("只解压txt文件完成!");
}
# 5. 遍历Zip条目
// 遍历Zip中的所有条目
try (ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8)) {
reader.read(entry -> {
Console.log("条目名:{}", entry.getName());
Console.log(" 大小:{}字节", entry.getSize());
Console.log(" 压缩大小:{}字节", entry.getCompressedSize());
Console.log(" 是目录:{}", entry.isDirectory());
});
}
# 6. 读取单个文件内容
// 读取Zip中的单个文件内容
try (ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8)) {
try (InputStream in = reader.get("test.txt")) {
if (in != null) {
String content = IoUtil.readUtf8(in);
Console.log("文件内容:{}", content);
} else {
Console.log("文件不存在!");
}
}
}
# 7. 调整ZipBomb防护设置
// 调整ZipBomb防护设置
try (ZipReader reader = ZipReader.of(FileUtil.file("test.zip"), CharsetUtil.CHARSET_UTF_8)) {
// 设置为200倍差异检查,或者-1表示不检查
reader.setMaxSizeDiff(200);
reader.readTo(FileUtil.file("output"));
Console.log("使用自定义ZipBomb检查设置解压完成!");
}
# 🎯 设计优势
# 1. 统一的API接口
- 封装了
ZipFile和ZipInputStream的差异,提供统一的访问接口 - 支持从文件或流创建ZipReader,适应不同场景
- 链式调用设计,代码更简洁流畅
# 2. 安全的ZipBomb防护
- 内置ZipBomb检测机制,默认检查差异倍数为100
- 可自定义检查倍数,或完全关闭检查
- 有效防止恶意压缩文件导致的系统资源耗尽
# 3. 灵活的过滤机制
- 支持通过
Predicate<ZipEntry>过滤要处理的文件 - 可以根据文件名、大小、类型等条件进行过滤
- 便于只处理需要的文件,提高效率
# 4. 自动资源管理
- 实现了
Closeable接口,可以配合try-with-resources使用 - 自动关闭底层资源,避免资源泄漏
- 统一的异常处理,简化上层代码
# 5. 跨平台兼容
- 自动处理不同操作系统的路径差异
- 修复了Windows系统下文件名包含特殊字符的问题
- 防止路径遍历攻击(Zip Slip)
# 6. 高效的流处理
- 内部使用高效的流处理机制
- 支持大文件处理,不会内存溢出
- 优化了Zip条目遍历和文件读取的性能
# 📌 注意事项
# 1. 编码问题
- 确保使用正确的编码方式,特别是处理非UTF-8编码的Zip文件
- 不同操作系统和压缩工具生成的Zip文件可能使用不同的编码
- 建议显式指定编码,避免默认编码导致的乱码问题
# 2. ZipBomb防护
- 默认开启ZipBomb检查,差异倍数为100
- 对于合法的高压缩比文件,可以适当调整差异倍数
- 对于可信来源的文件,可以关闭检查以提高性能
# 3. 资源关闭
- 务必关闭ZipReader实例,释放系统资源
- 建议使用try-with-resources语句自动管理资源
- 不关闭资源可能导致文件句柄泄漏
# 4. 大文件处理
- 对于超大Zip文件,建议使用流式处理
- 避免一次性读取所有内容到内存
- 可以使用过滤器只处理需要的文件
# 5. 路径安全
- ZipReader内置了路径遍历攻击防护
- 自动检查并修复不安全的文件路径
- 确保解压文件不会超出目标目录
# 🔄 与传统JDK方式的对比
# 1. 传统JDK方式(复杂)
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
// 传统方式解压Zip文件
String zipFilePath = "test.zip";
String destDirPath = "output";
// 方法1:使用ZipFile
try (ZipFile zipFile = new ZipFile(zipFilePath, StandardCharsets.UTF_8)) {
zipFile.stream().forEach(entry -> {
try {
File destFile = new File(destDirPath, entry.getName());
if (entry.isDirectory()) {
destFile.mkdirs();
} else {
destFile.getParentFile().mkdirs();
try (var in = zipFile.getInputStream(entry);
var out = new FileOutputStream(destFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
});
} catch (IOException e) {
e.printStackTrace();
}
// 方法2:使用ZipInputStream
try (ZipInputStream zis = new ZipInputStream(
new FileInputStream(zipFilePath), StandardCharsets.UTF_8)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
File destFile = new File(destDirPath, entry.getName());
if (entry.isDirectory()) {
destFile.mkdirs();
} else {
destFile.getParentFile().mkdirs();
try (var out = new FileOutputStream(destFile)) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
}
}
zis.closeEntry();
}
} catch (IOException e) {
e.printStackTrace();
}
# 2. Hutool方式(简洁)
import cn.hutool.v7.core.compress.ZipReader;
import cn.hutool.v7.core.io.FileUtil;
import cn.hutool.v7.core.util.Console;
// Hutool方式解压Zip文件
String zipFilePath = "test.zip";
String destDirPath = "output";
// 一行代码完成解压
try (ZipReader reader = ZipReader.of(FileUtil.file(zipFilePath), CharsetUtil.CHARSET_UTF_8)) {
reader.readTo(FileUtil.file(destDirPath));
Console.log("解压完成!");
}
// 带过滤器的解压
try (ZipReader reader = ZipReader.of(FileUtil.file(zipFilePath), CharsetUtil.CHARSET_UTF_8)) {
reader.readTo(FileUtil.file(destDirPath), entry -> !entry.isDirectory());
Console.log("只解压文件完成!");
}