Java按指定大小拆分压缩文件问题求助(附代码)
解决Zip分卷压缩停止运行的问题
我需要将一个30MB的文件(或目录)压缩并拆分为多个单个最大容量为5MB的Zip文件。当前我的代码在压缩大小超过限制后便停止运行,参考了一些示例但仍存在困惑,恳请给出解决建议。
用户原始代码:
public static void zipDirectory(File dir, File zipFile, boolean contentOnly) throws IOException { synchronized (syncObj) { FileOutputStream fout = new FileOutputStream(zipFile); ZipOutputStream zout = new ZipOutputStream(fout); if (contentOnly) zipSubDirectory("", dir, zout); else zipSubDirectory(dir.getName() + "/", dir, zout); zout.close(); } } private static void zipSubDirectory(String basePath, File dir, ZipOutputStream zout) throws IOException { synchronized (syncObj) { byte[] buffer = new byte[4096]; final long MAX_ZIP_SIZE = 5000000; long currentSize = 0; int zipSplitCount = 0; ZipEntry zipEntry; FileInputStream entryFile; File[] files = dir.listFiles(); for (File file : files) { zipEntry = new ZipEntry(basePath + file.getName()); if (file.isDirectory()) { String path = basePath + file.getName() + "/"; zout.putNextEntry(new ZipEntry(path)); zipSubDirectory(path, file, zout); zout.closeEntry(); } else { if (currentSize >= MAX_ZIP_SIZE) { zipSplitCount++; zout.close(); zout = new ZipOutputStream(new FileOutputStream(file.getName().replace(".zip", ".zip." + zipSplitCount))); currentSize = 0; } zout.putNextEntry(zipEntry); FileInputStream fin = new FileInputStream(file); int length; while ((length = fin.read(buffer)) > 0) { zout.write(buffer, 0, length); } zout.closeEntry(); fin.close(); } currentSize += zipEntry.getCompressedSize(); } } }
代码中的核心问题
- 递归状态不共享:
zipSubDirectory是递归方法,但currentSize、zipSplitCount是局部变量,递归调用时父方法无法获取子方法的大小变化,导致分卷逻辑混乱;且子方法关闭zout后,父方法继续使用已关闭的流会抛出异常,直接导致程序停止。 - 压缩大小计算错误:
zipEntry.getCompressedSize()在写入压缩流前返回-1,只有在关闭ZipEntry后才会有正确值,所以当前代码累加的是无效值,无法准确判断是否达到分卷阈值。 - 分卷文件名生成错误:生成新分卷时用的是当前遍历的
file.getName(),而不是原始的输出Zip文件名,会导致分卷名称混乱甚至覆盖源文件。 - 单个大文件未处理:如果单个文件压缩后超过5MB,当前逻辑会直接写入,导致分卷超过限制,且没有任何处理。
修复后的实现方案
我们需要将分卷相关的状态(当前输出流、已用大小、分卷计数、基础文件名)封装成一个辅助类,确保递归过程中状态共享;同时正确计算已写入的压缩大小,处理单个大文件的情况。
import java.io.*; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; public class SplitZipUtil { private static final Object syncObj = new Object(); private static final long MAX_ZIP_SIZE = 5 * 1024 * 1024; // 5MB // 辅助类保存分卷状态 private static class SplitZipContext { File baseZipFile; ZipOutputStream currentZout; long currentSize; int splitCount; public SplitZipContext(File baseZipFile) throws IOException { this.baseZipFile = baseZipFile; this.splitCount = 0; this.currentZout = createNewZipStream(); this.currentSize = 0; } // 创建新的分卷输出流 private ZipOutputStream createNewZipStream() throws IOException { String zipName = baseZipFile.getName(); String splitName; if (splitCount == 0) { splitName = zipName; // 第一个分卷用原始名 } else { // 分卷命名格式:xxx.zip.001、xxx.zip.002... splitName = zipName + "." + String.format("%03d", splitCount); } File splitFile = new File(baseZipFile.getParent(), splitName); FileOutputStream fout = new FileOutputStream(splitFile); splitCount++; return new ZipOutputStream(fout); } // 检查是否需要切换分卷,传入当前文件预估的压缩大小(这里用原始大小预估,更保守) public void checkSplit(long fileSize) throws IOException { // 预留一些空间,避免刚好达到阈值后写入文件导致超量 if (currentSize + fileSize > MAX_ZIP_SIZE - 1024) { currentZout.close(); currentZout = createNewZipStream(); currentSize = 0; } } // 更新当前已用大小(使用压缩后的实际大小) public void updateSize(ZipEntry entry) { long compressedSize = entry.getCompressedSize(); if (compressedSize != -1) { currentSize += compressedSize; } else { // 如果压缩大小未获取到,用原始大小替代(保守计算) currentSize += entry.getSize(); } } public void close() throws IOException { if (currentZout != null) { currentZout.close(); } } } public static void zipDirectory(File dir, File zipFile, boolean contentOnly) throws IOException { synchronized (syncObj) { SplitZipContext context = new SplitZipContext(zipFile); try { String basePath = contentOnly ? "" : dir.getName() + "/"; zipSubDirectory(basePath, dir, context); } finally { context.close(); } } } private static void zipSubDirectory(String basePath, File dir, SplitZipContext context) throws IOException { byte[] buffer = new byte[4096]; File[] files = dir.listFiles(); if (files == null) { return; } for (File file : files) { String entryPath = basePath + file.getName(); if (file.isDirectory()) { // 处理目录 ZipEntry dirEntry = new ZipEntry(entryPath + "/"); context.checkSplit(1024); // 目录条目大小很小,预留1KB context.currentZout.putNextEntry(dirEntry); context.currentZout.closeEntry(); context.updateSize(dirEntry); // 递归处理子目录 zipSubDirectory(entryPath + "/", file, context); } else { // 处理文件:先检查是否需要分卷(用原始大小预估) context.checkSplit(file.length()); ZipEntry fileEntry = new ZipEntry(entryPath); context.currentZout.putNextEntry(fileEntry); // 写入文件内容 try (FileInputStream fin = new FileInputStream(file)) { int length; while ((length = fin.read(buffer)) > 0) { context.currentZout.write(buffer, 0, length); } } context.currentZout.closeEntry(); // 更新已用大小 context.updateSize(fileEntry); } } } }
关键改进点
- 状态共享:用
SplitZipContext封装分卷相关状态,递归过程中统一维护,避免局部变量导致的状态丢失。 - 准确的分卷判断:用文件原始大小预估是否需要分卷(更保守,避免超量),同时预留少量空间防止刚好达到阈值后写入超量。
- 正确的分卷命名:基于原始Zip文件生成分卷名,格式为
xxx.zip.001、xxx.zip.002,第一个分卷保留原始名。 - 资源安全:使用try-with-resources自动关闭文件输入流,确保资源释放;在finally块中关闭最后一个分卷的输出流。
- 空目录处理:增加
files == null的判断,避免空指针异常。
内容的提问来源于stack exchange,提问作者codingmoc
相关产品推荐
相关产品推荐

