
本文详解为何 python 复制 zip 或 jar 文件后体积不一致,并提供两种可靠方案:强制禁用压缩(zip_stored)与零级压缩(zip_deflated + compresslevel=0),确保元数据、文件顺序及字节级一致性。
本文详解为何 python 复制 zip 或 jar 文件后体积不一致,并提供两种可靠方案:强制禁用压缩(zip_stored)与零级压缩(zip_deflated + compresslevel=0),确保元数据、文件顺序及字节级一致性。
在构建 Java 工具链或进行二进制补丁操作时,常需通过 Python 精确复刻 JAR(本质为 ZIP 格式)文件。但如示例所示,直接使用 zipfile.ZipFile(..., 'a') 并调用 writestr() 后,新生成的 patched.jar 比原文件小 13 KB——这并非偶然偏差,而是由 ZIP 写入行为的默认配置导致的字节级不一致。
根本原因在于:Python 的 zipfile 模块在创建新 ZIP 文件时,若未显式指定压缩方式与压缩等级,会采用默认策略(通常为 ZIP_DEFLATED + 自适应压缩级别),即使源文件本身是无压缩存储(ZIP_STORED)。更关键的是,'a'(append)模式在空文件上实际等效于 'w',但其内部初始化逻辑可能忽略原始 ZIP 的压缩属性、时间戳精度、扩展字段(如 UTF-8 路径标志位)、中央目录排序等细节——而 Java 运行时对 JAR 文件的校验(如签名验证、类加载器路径解析)高度依赖这些二进制层面的一致性。
要实现严格等长复制,必须主动控制 ZIP 写入参数。以下是两种经验证的可靠方法:
✅ 方法一:完全禁用压缩(推荐用于 JAR 复制)
JAR 规范明确要求类文件、资源等应以 STORED 方式存放(即原始字节直存,无压缩),这是保证可执行性和签名兼容性的前提。以下代码强制使用 ZIP_STORED 并关闭所有压缩逻辑:
import zipfile
with zipfile.ZipFile("original.jar", "r") as zin:
with zipfile.ZipFile("patched.jar", "w", zipfile.ZIP_STORED) as zout:
for entry in zin.infolist():
# 直接读取原始字节并写入,保持原有 CRC32、文件大小、修改时间等元数据
data = zin.read(entry.filename)
zout.writestr(entry, data)⚠️ 注意:zipfile.ZIP_STORED 已隐含 compresslevel=0,无需额外指定;且务必使用 with 语句确保文件句柄正确关闭,避免写入截断。
✅ 方法二:启用零级 Deflate 压缩(适用于需兼容特定工具链的场景)
某些构建系统或旧版 JDK 可能对 ZIP 中的 ZIP_DEFLATED 条目有特殊处理。此时可显式启用 Deflate,但将压缩等级设为 0(即仅做 LZ77 字面量编码,不进行 Huffman 编码优化,输出长度 ≈ 原始长度):
import zipfile
with zipfile.ZipFile("original.jar", "r") as zin:
with zipfile.ZipFile("patched.jar", "w", zipfile.ZIP_DEFLATED, compresslevel=0) as zout:
for entry in zin.infolist():
data = zin.read(entry.filename)
zout.writestr(entry, data)? 验证技巧:使用 unzip -l original.jar 与 unzip -l patched.jar 对比各条目 Stored/Defl:N 标识;用 cmp -l original.jar patched.jar | head -20 检查前若干字节差异;在 7-Zip 中检查“Characteristics”是否均含 UTF8 标志(可通过 entry.flag_bits |= 0x800 手动设置,但 JAR 规范通常不要求)。
? 关键总结
- 永远避免 'a' 模式进行全新复制:它不保证元数据继承,且易受缓存/状态残留影响;
- 显式声明 mode='w' + compression + compresslevel 是可控性的基石;
- 优先选用 ZIP_STORED:符合 JAR 规范,性能最优,体积最稳定;
- 务必使用上下文管理器(with):防止因异常导致 ZIP 文件损坏或不完整;
- 若需保留原始 ZIP 的注释、磁盘编号、AES 加密等高级特性,则需改用 zipfile.ZipInfo 手动构造条目并设置 extra 字段——但标准 JAR 场景中极少需要。
遵循上述任一方法,即可确保 patched.jar 与 original.jar 在文件大小、结构布局及运行时行为上完全一致。










