
ZipFile 读取大 ZIP 文件时内存暴涨?别 new ZipFile(path) 就完事
直接用 new ZipFile(String) 打开几个 GB 的 ZIP,很可能触发 OOM——它会把整个中央目录(Central Directory)加载进内存,而这个结构大小和 ZIP 内文件数量正相关,跟单个文件体积关系不大。真正压垮 JVM 的,往往是成千上万个条目,不是那个 2GB 的 data.bin。
- 优先改用
ZipInputStream流式遍历,不依赖中央目录,边读边解析,内存占用稳定在几百 KB 级别 - 如果必须用
ZipFile(比如要随机访问某 entry、或依赖getEntry()查找),务必搭配ZipFile(File, Charset)构造器,并传入明确的StandardCharsets.UTF_8,否则 Windows 上默认 CP1252 解码非 ASCII 路径会静默失败 - 用完立刻
close(),ZipFile持有底层RandomAccessFile句柄,不关会导致文件锁残留,Windows 下重命名/删除都失败
想解压单个大文件却卡住?ZipEntry.getSize() 返回 -1 是常态
getSize() 和 getCompressedSize() 在 ZIP64 或某些打包工具生成的归档里经常是 -1,这不是 bug,是规范允许的。别拿它做缓冲区预分配依据,更别用来判断“是否完整”。
- 解压时统一用固定大小缓冲区(如 8192 字节),循环
read()直到返回 -1 - 别信
entry.getSize()去创建byte[]数组——对 1.5GB 文件申请同尺寸数组,大概率直接OutOfMemoryError - 如果需校验解压后大小,应在写入目标
FileOutputStream后,用Files.size(path)获取实际字节数,而不是依赖 ZIP 元数据
中文路径乱码或找不到文件?Charset 参数不是可选的
ZIP 格式本身不强制规定文件名编码,Java 7+ 的 ZipFile 默认用平台编码(Windows 是 GBK),但绝大多数现代工具(7-Zip、macOS 归档工具、Gradle)都用 UTF-8 存路径。两者不匹配,getEntry("测试.txt") 就返回 null。
- 构造
ZipFile时必须显式传入StandardCharsets.UTF_8:new ZipFile(file, StandardCharsets.UTF_8) - 不要依赖
System.getProperty("file.encoding"),它不可靠,且与 ZIP 解码无关 - 如果不确定源 ZIP 编码,可先用
ZipInputStream逐个读ZipEntry,调用entry.getName().getBytes(StandardCharsets.UTF_8)再转回字符串试探,但这是兜底方案,性能差
解压速度上不去?别在循环里反复 new FileOutputStream
对每个 ZipEntry 都 new FileOutputStream(outFile) 看似自然,但在 SSD 或高 IOPS 环境下,频繁 open/close 文件句柄反而成为瓶颈,尤其当 ZIP 里有上万个小文件时。
立即学习“Java免费学习笔记(深入)”;
- 大文件解压场景下,目标路径应提前确保父目录存在:
Files.createDirectories(target.getParent()) - 用
Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)替代FileOutputStream,它更轻量,且支持原子覆盖选项 - 若目标是单个大文件,直接用
Files.copy(zipInputStream, target, StandardCopyOption.REPLACE_EXISTING),底层走零拷贝优化,比手动 read/write 快 20%–40%
最常被忽略的一点:ZIP 文件末尾可能有签名块(如 APK 的签名、JAR 的 MANIFEST.MF),ZipFile 能安全跳过,但 ZipInputStream 一旦读到末尾就停,不会主动跳过附加数据——这意味着用流式方式解压一个带签名的 APK,可能漏掉最后几十字节。真要处理这类文件,得自己解析 EOCD(End of Central Directory)位置,再截断流。这已经超出标准库能力了。










