You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 05:25:53