Java多线程压缩文件——优雅的自动分类至文件夹
在日常开发中,我们经常遇到文件归档压缩的场景。如果是大量文件、或者需要按照分类目录打包压缩,一般串行压缩不仅耗时长,而且容易阻塞主线程。
今天我们就通过 Apache Commons Compress + ParallelScatterZipCreator 实现一个 支持多线程压缩且自动按文件夹分类 的工具方法,性能可靠,可用于生产环境大文件归档。
📦 场景需求
-
按照不同的业务分类(如
资料A
、资料B
)分别存入不同文件夹下; -
支持压缩大文件(可超过 4GB);
-
多线程并发压缩,减少总耗时;
-
支持部分文件不存在时自动跳过;
-
稳定运行,避免内存溢出等问题。
🔧 核心依赖
我们采用的是 Apache Commons Compress 中的 ParallelScatterZipCreator
:
<dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.26.0</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency> </dependencies>
🔍 核心代码实现
以下是完整的 compressByFolder
工具方法,核心功能包括:
-
支持分类目录(folderName);
-
使用线程池提高并发压缩效率;
-
利用
ParallelScatterZipCreator
避免主线程阻塞; -
自动跳过不存在的文件,保证健壮性。
/** * 多线程压缩文件,按文件夹分类保存 * * @param folderFileMap 分类结构:key 为文件夹名,value 为文件列表 * @param zipFilePath 压缩输出路径 */ public static void compressByFolder(Map<String, List<Map<String, Object>>> folderFileMap, String zipFilePath) throws IOException, ExecutionException, InterruptedException { long start = System.currentTimeMillis(); // 1. 检查压缩目标目录 File zipFile = new File(zipFilePath); File parentDir = zipFile.getParentFile(); if (!parentDir.exists() && !parentDir.mkdirs()) { throw new IOException("无法创建目标目录: " + parentDir.getAbsolutePath()); } // 2. 初始化线程池 ExecutorService executor = new ThreadPoolExecutor( 5, 20, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100), Executors.defaultThreadFactory() ); try (OutputStream outputStream = Files.newOutputStream(Paths.get(zipFilePath)); ZipArchiveOutputStream zipArchiveOutputStream = new ZipArchiveOutputStream(outputStream)) { zipArchiveOutputStream.setEncoding("UTF-8"); zipArchiveOutputStream.setUseZip64(Zip64Mode.AsNeeded); // 支持大文件 zipArchiveOutputStream.setLevel(Deflater.DEFAULT_COMPRESSION); // 3. 创建并发压缩器 ParallelScatterZipCreator parallelScatterZipCreator = new ParallelScatterZipCreator(executor); for (Map.Entry<String, List<Map<String, Object>>> entry : folderFileMap.entrySet()) { String folderName = entry.getKey(); List<Map<String, Object>> fileMapList = entry.getValue(); for (Map<String, Object> fileMap : fileMapList) { try { String fileName = Optional.ofNullable(fileMap.get("fileName")) .filter(f -> !StringUtils.isBlank(f.toString())) .map(Object::toString) .orElse(""); String filePath = (String) fileMap.get("filePath"); File file = new File(filePath); if (StringUtils.isBlank(fileName)) { fileName = file.getName(); } if (!file.exists() || !file.isFile()) { System.err.println("文件不存在或非法文件: " + filePath); continue; } // 输入流供应器,懒加载 final InputStreamSupplier inputStreamSupplier = () -> { try { return new BufferedInputStream(new FileInputStream(file)); } catch (Exception e) { e.printStackTrace(); return new NullInputStream(0); // 空输入流,保证任务不中断 } }; // 拼接相对路径(/ 兼容) String fullPath = StringUtils.isBlank(folderName) ? fileName : folderName + "/" + fileName; ZipArchiveEntry zipArchiveEntry = new ZipArchiveEntry(fullPath.replace("\\", "/")); zipArchiveEntry.setMethod(ZipArchiveEntry.DEFLATED); zipArchiveEntry.setSize(file.length()); zipArchiveEntry.setUnixMode(UnixStat.FILE_FLAG | 436); // 添加到并发压缩任务中 parallelScatterZipCreator.addArchiveEntry(zipArchiveEntry, inputStreamSupplier); } catch (Exception e) { System.err.println("压缩文件出错:" + e.getMessage()); throw e; // 可选择记录日志后不抛出 } } } // 写入 zip 输出流 parallelScatterZipCreator.writeTo(zipArchiveOutputStream); } finally { executor.shutdown(); if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } long end = System.currentTimeMillis(); System.out.println("压缩完成,耗时:" + (end - start) + " ms"); }
🧪 示例调用
public static void main(String[] args) throws IOException, ExecutionException, InterruptedException { Map<String, List<Map<String, Object>>> folderFileMap = new HashMap<>(); List<Map<String, Object>> fileList = new ArrayList<>(); Map<String, Object> fileMap = new HashMap<>(); fileMap.put("fileName", "showdoc.doc"); fileMap.put("filePath", "D:\\Users\\lomo\\Downloads\\showdoc.doc"); fileList.add(fileMap); folderFileMap.put("test1", fileList); fileList = new ArrayList<>(); fileMap = new HashMap<>(); fileMap.put("fileName", "showdoc.doc"); fileMap.put("filePath", "D:\\Users\\lomo\\Downloads\\showdoc.doc"); fileList.add(fileMap); folderFileMap.put("test2", fileList); String zipFilePath = "D:/test.zip"; compressByFolder(folderFileMap, zipFilePath); }
📌 打包效果样例